diff --git a/0.2.1-rc1/ChangeLog b/0.2.1-rc1/ChangeLog
new file mode 100644
index 0000000..90e5c8a
--- /dev/null
+++ b/0.2.1-rc1/ChangeLog
@@ -0,0 +1,42 @@
+ChangeLog
+
+0.2.0
+
+Features
+ - Blogs can now be configured to use HTML instead of BBCode
+ - Tags now follow the rel-tag microformat standard
+ - Blog module is now translatable
+ - The entries shown on the BlogHolder when not browsing by date/tag can now be restricted to only show entries that are younger than a user specified age
+ - The RSS feed name can now be changed in the CMS
+ - Added support for receiving trackback pings
+ - Added SubscribeRSSWidget for linking directly to the blog RSS feed
+ - Tag widget title is now editable
+ - Added empty relationship statics so BlogEntry and BlogHolder can be decorated by a DataObjectDecorator
+ - Use pagination summary, so a full list of pages isnt generated
+ - Added Date variable to RSSWidget feed items, so Date can be used in template if wanted
+ - Cast Title variable on RSSWidget feed items, so Title can have Text functions called in the template if wanted
+
+Bugfixes
+ - Removed deprecated calls to sapphire, and made other fixes to support sapphire 2.3.0
+ - Don't use PHP short tags
+ - Don't display $Content on a BlogHolder, as it isnt editable in the CMS
+ - Prevent infinite loops when an RSSWidget on a blog points to itself
+ - Fix URL segment generation
+ - RSS feed is now sorted by date, newest first
+ - Fixed pagination
+ - Fixed summaries on BlogHolder
+ - Fixed issues with display by month when blog post is on last month of the day
+ - BlogEntry::Tags() was renamed to TagsCollection() to prevent conflicts with the database fields called Tags
+ - Fixed invalid use of single quotes in BlogEntryForm HTML
+ - Fixed extra
tags around blog content
+ - Default parent needs to be a string instead of an array
+ - Fixed escaping in BlogHolder
+ - Use themedCSS instead of hardlinking paths
+ - Fixed rss feed caching
+ - Fixed archive widget showing months and years for unpublished posts
+ - SetDate doesn't need to be called, as the date is automatically set
+
+
+0.1
+
+Initial release
diff --git a/0.2.1-rc1/README b/0.2.1-rc1/README
new file mode 100644
index 0000000..4e582d7
--- /dev/null
+++ b/0.2.1-rc1/README
@@ -0,0 +1,15 @@
+####################################################
+Blog Module
+####################################################
+
+# Maintainer Contact
+Andrew O'Neil (Nickname: aoneil)
+
+
+# Requirements
+SilverStripe minimum version 2.3.0
+
+# Documentation
+http://doc.silverstripe.com/doku.php?id=modules:blog
+
+
diff --git a/0.2.1-rc1/_config.php b/0.2.1-rc1/_config.php
new file mode 100644
index 0000000..62a2de0
--- /dev/null
+++ b/0.2.1-rc1/_config.php
@@ -0,0 +1,3 @@
+
diff --git a/0.2.1-rc1/code/ArchiveWidget.php b/0.2.1-rc1/code/ArchiveWidget.php
new file mode 100644
index 0000000..93227b4
--- /dev/null
+++ b/0.2.1-rc1/code/ArchiveWidget.php
@@ -0,0 +1,92 @@
+ 'Varchar'
+ );
+
+ static $defaults = array(
+ 'DisplayMode' => 'month'
+ );
+
+ static $title = 'Browse by Date';
+
+ static $cmsTitle = 'Blog Archive';
+
+ static $description = 'Show a list of months or years in which there are blog posts, and provide links to them.';
+
+ function getBlogHolder() {
+ $page = Director::currentPage();
+
+ if($page instanceof BlogHolder) {
+ return $page;
+ } elseif(($page instanceof BlogEntry) && ($page->getParent() instanceof BlogHolder)) {
+ return $page->getParent();
+ } else {
+ return DataObject::get_one('BlogHolder');
+ }
+ }
+
+ function getCMSFields() {
+ return new FieldSet(
+ new OptionsetField(
+ 'DisplayMode',
+ _t('ArchiveWidget.DispBY', 'Display by'),
+ array(
+ 'month' => _t('ArchiveWidget.MONTH', 'month'),
+ 'year' => _t('ArchiveWidget.YEAR', 'year')
+ )
+ )
+ );
+ }
+
+ function Dates() {
+ Requirements::themedCSS('archivewidget');
+
+ $results = new DataObjectSet();
+ $blogHolder = $this->getBlogHolder();
+ $id = $blogHolder->ID;
+
+ $stage = Versioned::current_stage();
+ $suffix = (!$stage || $stage == 'Stage') ? "" : "_$stage";
+
+
+ if($this->DisplayMode == 'month') {
+ $sqlResults = DB::query("SELECT DISTINCT MONTH(`Date`) AS `Month`, YEAR(`Date`) AS `Year` FROM `SiteTree$suffix` NATURAL JOIN `BlogEntry$suffix` WHERE `ParentID` = $id ORDER BY `Date` DESC");
+ } else {
+ $sqlResults = DB::query("SELECT DISTINCT YEAR(`Date`) AS `Year` FROM `SiteTree$suffix` NATURAL JOIN `BlogEntry$suffix` WHERE `ParentID` = $id ORDER BY `Date` DESC");
+ }
+
+ if(!$sqlResults) return new DataObjectSet();
+
+ foreach($sqlResults as $sqlResult) {
+ $date = new Date('Date');
+ $month = ($this->DisplayMode == 'month') ? (int)$sqlResult['Month'] : 1;
+
+ $date->setValue(array(
+ 'Day' => 1,
+ 'Month' => $month,
+ 'Year' => (int) $sqlResult['Year']
+ ));
+
+ if($this->DisplayMode == 'month') {
+ $link = $blogHolder->Link() . $sqlResult['Year']. '/' . sprintf("%'02d", $sqlResult['Month']);
+ } else {
+ $link = $blogHolder->Link() . $sqlResult['Year'];
+ }
+
+ $results->push(new ArrayData(array(
+ 'Date' => $date,
+ 'Link' => $link
+ )));
+ }
+
+ return $results;
+ }
+}
+?>
diff --git a/0.2.1-rc1/code/BlogEntry.php b/0.2.1-rc1/code/BlogEntry.php
new file mode 100644
index 0000000..abe4468
--- /dev/null
+++ b/0.2.1-rc1/code/BlogEntry.php
@@ -0,0 +1,216 @@
+ "SSDatetime",
+ "Author" => "Text",
+ "Tags" => "Text"
+ );
+
+ static $has_one = array(
+ );
+
+ static $has_many = array(
+ );
+
+ static $many_many = array(
+ );
+
+ static $defaults = array(
+ "ProvideComments" => true,
+ 'ShowInMenus' => false
+ );
+
+ static $extensions = array(
+ 'Hierarchy',
+ 'TrackBackDecorator',
+ "Versioned('Stage', 'Live')"
+ );
+
+ /**
+ * Is WYSIWYG editing allowed?
+ * @var boolean
+ */
+ static $allow_wysiwyg_editing = false;
+
+ /**
+ * Is WYSIWYG editing enabled?
+ * Used in templates.
+ *
+ * @return boolean
+ */
+ public function IsWYSIWYGEnabled() {
+ return self::$allow_wysiwyg_editing;
+ }
+
+ /**
+ * Overload so that the default date is today.
+ */
+ public function populateDefaults(){
+ parent::populateDefaults();
+
+ $this->setField('Date', date('Y-m-d H:i:s', strtotime('now')));
+ }
+
+ function getCMSFields() {
+ Requirements::javascript('blog/javascript/bbcodehelp.js');
+ Requirements::themedCSS('bbcodehelp');
+
+ $firstName = Member::currentUser() ? Member::currentUser()->FirstName : '';
+ $codeparser = new BBCodeParser();
+
+ $fields = parent::getCMSFields();
+
+ if(!self::$allow_wysiwyg_editing) {
+ $fields->removeFieldFromTab("Root.Content.Main","Content");
+ $fields->addFieldToTab("Root.Content.Main", new TextareaField("Content", _t("BlogEntry.CN", "Content"), 20));
+ }
+
+ $fields->addFieldToTab("Root.Content.Main", new PopupDateTimeField("Date", _t("BlogEntry.DT", "Date")),"Content");
+ $fields->addFieldToTab("Root.Content.Main", new TextField("Author", _t("BlogEntry.AU", "Author"), $firstName),"Content");
+
+ if(!self::$allow_wysiwyg_editing) {
+ $fields->addFieldToTab("Root.Content.Main", new LiteralField("BBCodeHelper", ""));
+ }
+
+ $fields->addFieldToTab("Root.Content.Main", new TextField("Tags", _t("BlogEntry.TS", "Tags (comma sep.)")),"Content");
+ return $fields;
+ }
+
+ /**
+ * Returns the tags added to this blog entry
+ */
+ function TagsCollection() {
+ $tags = split(" *, *", trim($this->Tags));
+ $output = new DataObjectSet();
+
+ foreach($tags as $tag) {
+ $output->push(new ArrayData(array(
+ 'Tag' => $tag,
+ 'Link' => $this->getParent()->Link() . 'tag/' . urlencode($tag)
+ )));
+ }
+
+ if($this->Tags) {
+ return $output;
+ }
+ }
+
+ /**
+ * Get the sidebar from the BlogHolder.
+ */
+ function SideBar() {
+ return $this->getParent()->SideBar();
+ }
+
+ /**
+ * Get a bbcode parsed summary of the blog entry
+ */
+ function ParagraphSummary(){
+ if(self::$allow_wysiwyg_editing) {
+ return $this->obj('Content')->FirstParagraph('html');
+ } else {
+ $parser = new BBCodeParser($this->Content);
+ $html = new HTMLText('Content');
+ $html->setValue($parser->parse());
+ return $html->FirstParagraph('html');
+ }
+ }
+
+ /**
+ * Get the bbcode parsed content
+ */
+ function ParsedContent() {
+ if(self::$allow_wysiwyg_editing) {
+ return $this->obj('Content');
+ } else {
+ $parser = new BBCodeParser($this->Content);
+ $content = new Text('Content');
+ $content->value = $parser->parse();
+
+ return $content;
+ }
+ }
+
+ /**
+ * Link for editing this blog entry
+ */
+ function EditURL() {
+ return $this->getParent()->Link('post') . '/' . $this->ID . '/';
+ }
+
+ /**
+ * Check to see if trackbacks are enabled.
+ */
+ function TrackBacksEnabled() {
+ return $this->getParent()->TrackBacksEnabled;
+ }
+
+ function trackbackping() {
+ if($this->TrackBacksEnabled()) {
+ return $this->extInstance('TrackBackDecorator')->trackbackping();
+ } else {
+ Director::redirect($this->Link());
+ }
+ }
+
+ /**
+ * Call this to enable WYSIWYG editing on your blog entries.
+ * By default the blog uses BBCode
+ */
+ static function allow_wysiwyg_editing() {
+ self::$allow_wysiwyg_editing = true;
+ }
+}
+
+class BlogEntry_Controller extends Page_Controller {
+ static $allowed_actions = array(
+ 'trackbackping',
+ 'unpublishPost',
+ 'PageComments'
+ );
+
+ function init() {
+ parent::init();
+
+ Requirements::themedCSS('blog');
+ }
+
+ /**
+ * Gets a link to unpublish the blog entry
+ */
+ function unpublishPost() {
+ if(!Permission::check('ADMIN')) {
+ Security::permissionFailure(
+ $this,
+ 'Unpublishing blogs is an administrator task. Please log in.'
+ );
+ } else {
+ $SQL_id = (int) $this->ID;
+
+ $page = DataObject::get_by_id('SiteTree', $SQL_id);
+ $page->deleteFromStage('Live');
+ $page->flushCache();
+
+ $page = DataObject::get_by_id('SiteTree', $SQL_id);
+ $page->Status = 'Unpublished';
+
+ Director::redirect($this->getParent()->Link());
+ }
+ }
+
+}
+?>
diff --git a/0.2.1-rc1/code/BlogHolder.php b/0.2.1-rc1/code/BlogHolder.php
new file mode 100644
index 0000000..ec71169
--- /dev/null
+++ b/0.2.1-rc1/code/BlogHolder.php
@@ -0,0 +1,381 @@
+ 'Varchar',
+ 'Name' => 'Varchar',
+ 'TrackBacksEnabled' => 'Boolean'
+ );
+
+ static $has_one = array(
+ "SideBar" => "WidgetArea"
+ );
+
+ static $has_many = array(
+ );
+
+ static $many_many = array(
+ );
+
+ static $allowed_children = array(
+ 'BlogEntry'
+ );
+
+ function getCMSFields() {
+ $fields = parent::getCMSFields();
+ $fields->removeFieldFromTab("Root.Content.Main","Content");
+ $fields->addFieldToTab("Root.Content.Widgets", new WidgetAreaEditor("SideBar"));
+ $fields->addFieldToTab("Root.Content.Main", new TextField("Name", "Name of blog"));
+
+ $fields->addFieldToTab('Root.Content.Main', new DropdownField('LandingPageFreshness', 'When you first open the blog, how many entries should I show', array(
+ "" => "All entries",
+ "1 MONTH" => "Last month's entries",
+ "2 MONTH" => "Last 2 months' entries",
+ "3 MONTH" => "Last 3 months' entries",
+ "4 MONTH" => "Last 4 months' entries",
+ "5 MONTH" => "Last 5 months' entries",
+ "6 MONTH" => "Last 6 months' entries",
+ "7 MONTH" => "Last 7 months' entries",
+ "8 MONTH" => "Last 8 months' entries",
+ "9 MONTH" => "Last 9 months' entries",
+ "10 MONTH" => "Last 10 months' entries",
+ "11 MONTH" => "Last 11 months' entries",
+ "12 MONTH" => "Last year's entries"
+ )));
+
+ $fields->addFieldToTab('Root.Content.Main', new CheckboxField('TrackBacksEnabled', 'Enable TrackBacks'));
+
+ return $fields;
+ }
+
+ /**
+ * Get entries in this blog.
+ * @param string limit A clause to insert into the limit clause.
+ * @param string tag Only get blog entries with this tag
+ * @param string date Only get blog entries on this date - either a year, or a year-month eg '2008' or '2008-02'
+ * @return DataObjectSet
+ */
+ public function Entries($limit = '', $tag = '', $date = '') {
+ $tagCheck = '';
+ $dateCheck = '';
+
+ if($tag) {
+ $SQL_tag = Convert::raw2sql($tag);
+ $tagCheck = "AND `BlogEntry`.Tags LIKE '%$SQL_tag%'";
+ }
+
+ if($date) {
+ if(strpos($date, '-')) {
+ $year = (int) substr($date, 0, strpos($date, '-'));
+ $month = (int) substr($date, strpos($date, '-') + 1);
+
+ if($year && $month) {
+ $dateCheck = "AND MONTH(`BlogEntry`.Date) = $month AND YEAR(`BlogEntry`.Date) = $year";
+ }
+ } else {
+ $year = (int) $date;
+ if($year) {
+ $dateCheck = "AND YEAR(`BlogEntry`.Date) = $year";
+ }
+ }
+ }
+
+ return DataObject::get("Page","`ParentID` = $this->ID $tagCheck $dateCheck","`BlogEntry`.Date DESC",'',"$limit");
+ }
+
+ /**
+ * Only display the blog entries that have the specified tag
+ */
+ function ShowTag() {
+ if(Director::urlParam('Action') == 'tag') {
+ return Convert::raw2xml(Director::urlParam('ID'));
+ }
+ }
+
+ /**
+ * Check if url has "/post"
+ */
+ function isPost() {
+ return Director::urlParam('Action') == 'post';
+ }
+
+ /**
+ * Link for creating a new blog entry
+ */
+ function postURL(){
+ return $this->Link('post');
+ }
+
+ /**
+ * Create default blog setup
+ */
+ function requireDefaultRecords() {
+ parent::requireDefaultRecords();
+
+ if(!DataObject::get_one('BlogHolder')) {
+ $blogholder = new BlogHolder();
+ $blogholder->Title = "Blog";
+ $blogholder->URLSegment = "blog";
+ $blogholder->Status = "Published";
+
+ $widgetarea = new WidgetArea();
+ $widgetarea->write();
+
+ $blogholder->SideBarID = $widgetarea->ID;
+ $blogholder->write();
+ $blogholder->publish("Stage", "Live");
+
+ $managementwidget = new BlogManagementWidget();
+ $managementwidget->ParentID = $widgetarea->ID;
+ $managementwidget->write();
+
+ $tagcloudwidget = new TagCloudWidget();
+ $tagcloudwidget->ParentID = $widgetarea->ID;
+ $tagcloudwidget->write();
+
+ $archivewidget = new ArchiveWidget();
+ $archivewidget->ParentID = $widgetarea->ID;
+ $archivewidget->write();
+
+ $widgetarea->write();
+
+ $blog = new BlogEntry();
+ $blog->Title = _t('BlogHolder.SUCTITLE', "SilverStripe blog module successfully installed");
+ $blog->URLSegment = 'sample-blog-entry';
+ $blog->Tags = _t('BlogHolder.SUCTAGS',"silverstripe, blog");
+ $blog->Content = _t('BlogHolder.SUCCONTENT',"Congratulations, the SilverStripe blog module has been successfully installed. This blog entry can be safely deleted. You can configure aspects of your blog (such as the widgets displayed in the sidebar) in [url=admin]the CMS[/url].");
+ $blog->Status = "Published";
+ $blog->ParentID = $blogholder->ID;
+ $blog->write();
+ $blog->publish("Stage", "Live");
+
+ Database::alteration_message("Blog page created","created");
+ }
+ }
+}
+
+class BlogHolder_Controller extends Page_Controller {
+
+ static $allowed_actions = array(
+ 'postblog' => 'BLOGMANAGEMENT',
+ 'post' => 'BLOGMANAGEMENT',
+ 'BlogEntryForm' => 'BLOGMANAGEMENT',
+ 'rss',
+ 'tag',
+ 'showarchive',
+ );
+
+ function init() {
+ parent::init();
+
+ // This will create a tag point to the RSS feed
+ RSSFeed::linkToFeed($this->Link() . "rss", _t('BlogHolder.RSSFEED',"RSS feed of this blog"));
+ Requirements::themedCSS("blog");
+ Requirements::themedCSS("bbcodehelp");
+
+ }
+
+ function BlogEntries($limit = 10) {
+ $start = isset($_GET['start']) ? (int) $_GET['start'] : 0;
+ $tag = '';
+ $date = '';
+
+ if(Director::urlParams()) {
+ if(Director::urlParam('Action') == 'tag') {
+ $tag = Director::urlParam('ID');
+ } else {
+ $year = Director::urlParam('Action');
+ $month = Director::urlParam('ID');
+
+ if($month && is_numeric($month) && $month >= 1 && $month <= 12 && is_numeric($year)) {
+ $date = "$year-$month";
+ } else if(is_numeric($year)) {
+ $date = $year;
+ }
+ }
+ }
+
+ return $this->Entries("$start,$limit", $tag, $date);
+ }
+
+ /**
+ * Gets the archived blogs for a particular month or year, in the format /year/month/ eg: /2008/10/
+ */
+ function showarchive() {
+ $month = addslashes($this->urlParams['ID']);
+ return array(
+ "Children" => DataObject::get('SiteTree', "ParentID = $this->ID AND DATE_FORMAT(`BlogEntry`.`Date`, '%Y-%M') = '$month'"),
+ );
+ }
+
+ function ArchiveMonths() {
+ $months = DB::query("SELECT DISTINCT DATE_FORMAT(`BlogEntry`.`Date`, '%M') AS `Month`, DATE_FORMAT(`BlogEntry`.`Date`, '%Y') AS `Year` FROM `BlogEntry` ORDER BY `BlogEntry`.`Date` DESC");
+ $output = new DataObjectSet();
+ foreach($months as $month) {
+ $month['Link'] = $this->Link() . "showarchive/$month[Year]-$month[Month]";
+ $output->push(new ArrayData($month));
+ }
+
+ return $output;
+ }
+
+ function tag() {
+ if($this->ShowTag()) {
+ return array(
+ 'Tag' => $this->ShowTag()
+ );
+ } else {
+ return array();
+ }
+ }
+
+ /**
+ * Get the rss feed for this blog holder's entries
+ */
+ function rss() {
+ global $project;
+
+ $blogName = $this->Name;
+ $altBlogName = $project . ' blog';
+
+ $entries = $this->Entries(20);
+
+ if($entries) {
+ $rss = new RSSFeed($entries, $this->Link(), ($blogName ? $blogName : $altBlogName), "", "Title", "ParsedContent");
+ $rss->outputToBrowser();
+ }
+ }
+
+ /**
+ * Return list of usable tags for help
+ */
+ function BBTags() {
+ return BBCodeParser::usable_tags();
+ }
+
+ /**
+ * Post a new blog entry
+ */
+ function post(){
+ if(!Permission::check('BLOGMANAGEMENT')) return Security::permissionFailure();
+
+ $page = $this->customise(array(
+ 'Content' => false,
+ 'Form' => $this->BlogEntryForm()
+ ));
+
+ return $page->renderWith('Page');
+ }
+
+ function defaultAction($action) {
+ // Protection against infinite loops when an RSS widget pointing to this page is added to this page
+ if(stristr($_SERVER['HTTP_USER_AGENT'], 'SimplePie')) {
+ return $this->rss();
+ }
+
+ return parent::defaultAction($action);
+ }
+
+ /**
+ * A simple form for creating blog entries
+ */
+ function BlogEntryForm() {
+ if(!Permission::check('BLOGMANAGEMENT')) return Security::permissionFailure();
+
+ Requirements::javascript('jsparty/behaviour.js');
+ Requirements::javascript('jsparty/prototype.js');
+ Requirements::javascript('jsparty/scriptaculous/effects.js');
+ Requirements::javascript('cms/javascript/PageCommentInterface.js');
+ Requirements::javascript('blog/javascript/bbcodehelp.js');
+
+ $id = 0;
+ if(Director::urlParam('ID')) {
+ $id = (int) Director::urlParam('ID');
+ }
+
+ $codeparser = new BBCodeParser();
+ $membername = Member::currentMember() ? Member::currentMember()->getName() : "";
+
+ if(BlogEntry::$allow_wysiwyg_editing) {
+ $contentfield = new HtmlEditorField("BlogPost", _t("BlogEntry.CN"));
+ } else {
+ $contentfield = new CompositeField(
+ new LiteralField("BBCodeHelper",""._t("BlogEntry.BBH")."
" ),
+ new TextareaField("BlogPost", _t("BlogEntry.CN"),20), // This is called BlogPost as the id #Content is generally used already
+ new LiteralField("BBCodeTags","".$codeparser->useable_tagsHTML()."
")
+ );
+ }
+
+ if(class_exists('TagField')) {
+ $tagfield = new TagField('Tags', null, null, 'BlogEntry');
+ $tagfield->setSeparator(', ');
+ } else {
+ $tagfield = new TextField('Tags');
+ }
+
+ $fields = new FieldSet(
+ new HiddenField("ID", "ID"),
+ new TextField("Title",_t('BlogHolder.SJ', "Subject")),
+ new TextField("Author",_t('BlogEntry.AU'),$membername),
+ $contentfield,
+ $tagfield,
+ new LiteralField("Tagsnote"," "._t('BlogHolder.TE', "For example: sport, personal, science fiction")." " .
+ _t('BlogHolder.SPUC', "Please separate tags using commas.")." ")
+ );
+
+ $submitAction = new FormAction('postblog', _t('BlogHolder.POST', 'Post blog entry'));
+ $actions = new FieldSet($submitAction);
+ $validator = new RequiredFields('Title','Content');
+
+ $form = new Form($this, 'BlogEntryForm',$fields, $actions,$validator);
+
+ if($id != 0) {
+ $entry = DataObject::get_by_id('BlogEntry', $id);
+ $form->loadNonBlankDataFrom($entry);
+ $form->datafieldByName('BlogPost')->setValue($entry->Content);
+ } else {
+ $form->loadNonBlankDataFrom(array("Author" => Cookie::get("BlogHolder_Name")));
+ }
+
+ return $form;
+ }
+
+ function postblog($data, $form) {
+ if(!Permission::check('BLOGMANAGEMENT')) return Security::permissionFailure();
+
+ Cookie::set("BlogHolder_Name", $data['Author']);
+ $blogentry = false;
+
+ if(isset($data['ID']) && $data['ID']) {
+ $blogentry = DataObject::get_by_id("BlogEntry", $data['ID']);
+ }
+
+ if(!$blogentry) {
+ $blogentry = new BlogEntry();
+ }
+
+ $form->saveInto($blogentry);
+ $blogentry->ParentID = $this->ID;
+ $blogentry->Content = $form->datafieldByName('BlogPost')->dataValue();
+
+ $blogentry->Status = "Published";
+ $blogentry->writeToStage("Stage");
+ $blogentry->publish("Stage", "Live");
+
+ Director::redirect($this->Link());
+ }
+}
+
+
+?>
diff --git a/0.2.1-rc1/code/BlogManagementWidget.php b/0.2.1-rc1/code/BlogManagementWidget.php
new file mode 100644
index 0000000..0fbb41e
--- /dev/null
+++ b/0.2.1-rc1/code/BlogManagementWidget.php
@@ -0,0 +1,56 @@
+value();
+ if($unmoderatedcount == 1) {
+ return _t("BlogManagementWidget.UNM1", "You have 1 unmoderated comment");
+ } else if($unmoderatedcount > 1) {
+ return sprintf(_t("BlogManagementWidget.UNMM", "You have %i unmoderated comments"), $unmoderatedcount);
+ } else {
+ return _t("BlogManagementWidget.COMADM", "Comment administration");
+ }
+ }
+
+ function CommentLink() {
+ $unmoderatedcount = DB::query("SELECT COUNT(*) FROM PageComment WHERE NeedsModeration=1")->value();
+
+ if($unmoderatedcount > 0) {
+ return "admin/comments/unmoderated";
+ } else {
+ return "admin/comments";
+ }
+ }
+
+ function WidgetHolder() {
+ if(Permission::check("ADMIN")) {
+ return $this->renderWith("WidgetHolder");
+ }
+ }
+
+ function PostLink() {
+ $blogholder = $this->getBlogHolder();
+
+ return $blogholder->Link('post');
+ }
+
+ function getBlogHolder() {
+ $page = Director::currentPage();
+
+ if($page->is_a("BlogHolder")) {
+ return $page;
+ } else if($page->is_a("BlogEntry") && $page->getParent()->is_a("BlogHolder")) {
+ return $page->getParent();
+ } else {
+ return DataObject::get_one("BlogHolder");
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/0.2.1-rc1/code/RSSWidget.php b/0.2.1-rc1/code/RSSWidget.php
new file mode 100644
index 0000000..ed9282e
--- /dev/null
+++ b/0.2.1-rc1/code/RSSWidget.php
@@ -0,0 +1,75 @@
+ "Text",
+ "RssUrl" => "Text",
+ "NumberToShow" => "Int"
+ );
+
+ static $defaults = array(
+ "NumberToShow" => 10,
+ "RSSTitle" => 'RSS Feed'
+ );
+ static $cmsTitle = "RSS Feed";
+ static $description = "Shows the latest entries of a RSS feed.";
+
+ /**
+ * If the RssUrl is relative, convert it to absolute with the
+ * current baseURL to avoid confusing simplepie.
+ * Passing relative URLs to simplepie will result
+ * in strange DNS lookups and request timeouts.
+ *
+ * @return string
+ */
+ function getAbsoluteRssUrl() {
+ $urlParts = parse_url($this->RssUrl);
+ if(!isset($urlParts['host']) || !$urlParts['host']) {
+ return Director::absoluteBaseURL() . $this->RssUrl;
+ } else {
+ return $this->RssUrl;
+ }
+ }
+
+ function getCMSFields() {
+ return new FieldSet(
+ new TextField("RSSTitle", _t('RSSWidget.CT', "Custom title for the feed")),
+ new TextField("RssUrl", _t('RSSWidget.URL', "URL of RSS Feed")),
+ new NumericField("NumberToShow", _t('RSSWidget.NTS', "Number of Items to show"))
+ );
+ }
+ function Title() {
+ return ($this->RSSTitle) ? $this->RSSTitle : 'RSS Feed';
+ }
+
+ function FeedItems() {
+ $output = new DataObjectSet();
+
+ include_once(Director::getAbsFile(SAPPHIRE_DIR . '/thirdparty/simplepie/SimplePie.php'));
+
+ $t1 = microtime(true);
+ $this->feed = new SimplePie($this->AbsoluteRssUrl, TEMP_FOLDER);
+ $this->feed->init();
+ if($items = $this->feed->get_items(0, $this->NumberToShow)) {
+ foreach($items as $item) {
+
+ // Cast the Date
+ $date = new Date('Date');
+ $date->setValue($item->get_date());
+
+ // Cast the Title
+ $title = new Text('Title');
+ $title->setValue($item->get_title());
+
+ $output->push(new ArrayData(array(
+ 'Title' => $title,
+ 'Date' => $date,
+ 'Link' => $item->get_link()
+ )));
+ }
+ return $output;
+ }
+ }
+}
+
+?>
\ No newline at end of file
diff --git a/0.2.1-rc1/code/SubscribeRSSWidget.php b/0.2.1-rc1/code/SubscribeRSSWidget.php
new file mode 100644
index 0000000..e3f8c07
--- /dev/null
+++ b/0.2.1-rc1/code/SubscribeRSSWidget.php
@@ -0,0 +1,52 @@
+getParent() instanceof BlogHolder)) {
+ return $page->getParent();
+ } else {
+ return DataObject::get_one('BlogHolder');
+ }
+ }
+
+ /**
+ * Return an absolute URL based on the BlogHolder
+ * that this widget is located on.
+ *
+ * @return string
+ */
+ function RSSLink() {
+ Requirements::themedCSS('subscribersswidget');
+ $blogHolder = $this->getBlogHolder();
+ if($blogHolder) {
+ return $blogHolder->Link() . 'rss';
+ }
+ }
+
+}
+
+?>
\ No newline at end of file
diff --git a/0.2.1-rc1/code/TagCloudWidget.php b/0.2.1-rc1/code/TagCloudWidget.php
new file mode 100644
index 0000000..46e42d1
--- /dev/null
+++ b/0.2.1-rc1/code/TagCloudWidget.php
@@ -0,0 +1,158 @@
+ "Varchar",
+ "Limit" => "Int",
+ "Sortby" => "Varchar"
+ );
+
+ static $defaults = array(
+ "Title" => "Tag Cloud",
+ "Limit" => "0",
+ "Sortby" => "alphabet"
+ );
+
+ static $cmsTitle = "Tag Cloud";
+ static $description = "Shows a tag cloud of tags on your blog.";
+
+ function getBlogHolder() {
+ $page = Director::currentPage();
+
+ if($page->is_a("BlogHolder")) {
+ return $page;
+ } else if($page->is_a("BlogEntry") && $page->getParent()->is_a("BlogHolder")) {
+ return $page->getParent();
+ } else {
+ return DataObject::get_one("BlogHolder");
+ }
+ }
+
+
+ function getCMSFields() {
+ return new FieldSet(
+ new TextField("Title", _t("TagCloudWidget.TILE", "Title")),
+ new TextField("Limit", _t("TagCloudWidget.LIMIT", "Limit number of tags")),
+ new OptionsetField("Sortby",_t("TagCloudWidget.SORTBY","Sort by"),array("alphabet"=>_t("TagCloudWidget.SBAL", "alphabet"),"frequency"=>_t("TagCloudWidget.SBFREQ", "frequency")))
+ );
+ }
+
+ function Title() {
+ return $this->Title ? $this->Title : 'Tag Cloud';
+ }
+
+ function TagsCollection() {
+ Requirements::css("blog/css/tagcloud.css");
+
+ $allTags = array();
+ $max = 0;
+ $blogHolder = $this->getBlogHolder();
+
+ $entries = $blogHolder->Entries();
+
+ if($entries) {
+ foreach($entries as $entry) {
+ $theseTags = split(" *, *", strtolower(trim($entry->Tags)));
+ foreach($theseTags as $tag) {
+ if($tag != "") {
+ $allTags[$tag] = isset($allTags[$tag]) ? $allTags[$tag] + 1 : 1; //getting the count into key => value map
+ $max = ($allTags[$tag] > $max) ? $allTags[$tag] : $max;
+ }
+ }
+ }
+
+ if($allTags) {
+ //TODO: move some or all of the sorts to the database for more efficiency
+ if($this->Limit > 0){
+ uasort($allTags, array($this, "column_sort_by_popularity")); //sort by popularity
+ $allTags = array_slice($allTags, 0, $this->Limit);
+ }
+ if($this->Sortby == "alphabet"){
+ $this->natksort($allTags);
+ }
+
+ $sizes = array();
+ foreach($allTags as $tag => $count){
+ $sizes[$count] = true;
+ }
+ $numsizes = count($sizes)-1; //Work out the number of different sizes
+ if($numsizes > 5){$numsizes = 5;}
+ foreach($allTags as $tag => $count) {
+
+ $popularity = floor($count / $max * $numsizes);
+
+ switch($popularity) {
+ case 0:
+ $class = "not-popular";
+ break;
+ case 1:
+ $class = "not-very-popular";
+ break;
+ case 2:
+ $class = "somewhat-popular";
+ break;
+ case 3:
+ $class = "popular";
+ break;
+ case 4:
+ $class = "very-popular";
+ break;
+ case 5:
+ $class = "ultra-popular";
+ break;
+ default:
+ $class = "broken";
+ break;
+ }
+
+ $allTags[$tag] = array(
+ "Tag" => $tag,
+ "Count" => $count,
+ "Class" => $class,
+ "Link" => $blogHolder->Link() . 'tag/' . urlencode($tag)
+ );
+ }
+ }
+
+ $output = new DataObjectSet();
+ foreach($allTags as $tag => $fields) {
+ $output->push(new ArrayData($fields));
+ }
+ return $output;
+ }
+
+ return;
+ }
+
+ /**
+ * Helper method to compare 2 Vars to work out the results.
+ * @param mixed
+ * @param mixed
+ * @return int
+ */
+ private function column_sort_by_popularity($a, $b){
+ if($a == $b) {
+ $result = 0;
+ }
+ else {
+ $result = $b - $a;
+ }
+ return $result;
+ }
+
+ private function natksort(&$aToBeSorted) {
+ $aResult = array();
+ $aKeys = array_keys($aToBeSorted);
+ natcasesort($aKeys);
+ foreach ($aKeys as $sKey) {
+ $aResult[$sKey] = $aToBeSorted[$sKey];
+ }
+ $aToBeSorted = $aResult;
+
+ return true;
+ }
+}
+
+
+
+?>
diff --git a/0.2.1-rc1/code/TrackBackDecorator.php b/0.2.1-rc1/code/TrackBackDecorator.php
new file mode 100644
index 0000000..436a48b
--- /dev/null
+++ b/0.2.1-rc1/code/TrackBackDecorator.php
@@ -0,0 +1,53 @@
+ array(
+ 'TrackBacks' => 'TrackBackPing'
+ )
+ );
+ }
+
+ function updateMetaTags(&$tags) {
+ $tags .= $this->owner->renderWith('TrackBackRdf');
+ }
+
+ function TrackBackPingLink() {
+ return $this->owner->AbsoluteLink() . 'trackbackping';
+ }
+
+ function trackbackping() {
+ $error = 0;
+ $message = '';
+
+ if(!(isset($_POST['url']) && $_POST['url'])) {
+ $error = 1;
+ $message = 'Missing required POST parameter \'url\'.';
+ } else {
+ $trackbackping = new TrackBackPing();
+ $trackbackping->Url = $_POST['url'];
+ if(isset($_POST['title']) && $_POST['title']) {
+ $trackbackping->Title = $_POST['title'];
+ }
+ if(isset($_POST['excerpt']) && $_POST['excerpt']) {
+ $trackbackping->Excerpt = $_POST['excerpt'];
+ }
+ if(isset($_POST['blog_name']) && $_POST['blog_name']) {
+ $trackbackping->BlogName = $_POST['blog_name'];
+ }
+ $trackbackping->PageID = $this->owner->ID;
+ $trackbackping->write();
+ }
+
+ $returnData = new ArrayData(array(
+ 'Error' => $error,
+ 'Message' => $message
+ ));
+
+ return $returnData->renderWith('TrackBackPingReturn');
+ }
+}
+
+
+?>
diff --git a/0.2.1-rc1/code/TrackBackPing.php b/0.2.1-rc1/code/TrackBackPing.php
new file mode 100644
index 0000000..900a50a
--- /dev/null
+++ b/0.2.1-rc1/code/TrackBackPing.php
@@ -0,0 +1,16 @@
+ 'Varchar',
+ 'Excerpt' => 'Text',
+ 'Url' => 'Varchar',
+ 'BlogName' => 'Varchar'
+ );
+
+ static $has_one = array(
+ 'Page' => 'Page'
+ );
+}
+
+?>
diff --git a/0.2.1-rc1/code/import/TypoImport.php b/0.2.1-rc1/code/import/TypoImport.php
new file mode 100644
index 0000000..11e264d
--- /dev/null
+++ b/0.2.1-rc1/code/import/TypoImport.php
@@ -0,0 +1,152 @@
+Title = "imported blog";
+
+ // write it!
+ $bholder->write();
+ $bholder->publish("Stage", "Live");
+
+ // get the typo articles
+ $result = pg_query($dbconn, "SELECT * FROM contents WHERE type='Article'");
+
+ while ($row = pg_fetch_row($result)) {
+
+ // title [1]
+ // author [2]
+ // body [3]
+ // body_html [4] (type rendered and cached the html here. This is the preferred blog entry content for migration)
+ // keywords (space separated) [7] (tags table is just a list of the unique variants of these keywords)
+ // created_at [8]
+ // permalink [12] (this is like the url in sitetree, prolly not needed)
+ // email [18] (address of the commenter)
+ // url [19] (url of the commenter)
+
+ $title = $row[1];
+ $author = $row[2];
+ $blog_entry = $row[4];
+ $keywords = $row[7];
+ $created_at = $row[8];
+
+ // sometimes it's empty. If it is, grab the body
+ if ($blog_entry == ""){
+ // use "body"
+ $blog_entry = $row[3];
+ }
+ echo "blog_entry: $blog_entry";
+ echo " \n";
+
+ // put the typo blog entry in the SS database
+ $newEntry = new BlogEntry();
+ $newEntry->Title = $title;
+ $newEntry->Author = $author;
+ $newEntry->Content = $blog_entry;
+ $newEntry->Tags = $keywords;
+ $newEntry->Date = $created_at;
+
+ // tie each blog entry back to the blogholder we created initially
+ $newEntry->ParentID = $bholder->ID;
+
+ // write it!
+ $newEntry->write();
+ $newEntry->publish("Stage", "Live");
+
+ // grab the id so we can get the comments
+ $old_article_id = $row[0];
+
+ // get the comments
+ $result2 = pg_query($dbconn, "SELECT * FROM contents WHERE type = 'Comment' AND article_id = $old_article_id");
+
+ while ($row2 = pg_fetch_row($result2)) {
+ // grab the body_html
+ $comment = $row2[4];
+
+ // sometimes it's empty. If it is, grab the body
+ if ($comment == ""){
+ // use "body"
+ $comment = $row2[3];
+ }
+
+
+
+
+ $Cauthor = $row2[2];
+ $Ccreated_at = $row2[8];
+
+ // put the typo blog comment in the SS database
+ $newCEntry = new PageComment();
+ $newCEntry->Name = $Cauthor;
+ $newCEntry->Comment = $comment;
+ $newCEntry->Created = $created_at;
+
+ // need to grab the newly inserted blog entry's id
+ $newCEntry->ParentID = $newEntry->ID;
+
+ // write it!
+ $newCEntry->write();
+
+ echo "comment: $comment";
+ echo " \n";
+ }
+
+ $newEntry->flushCache();
+
+ // fix up the specialchars
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"×\", \"x\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"’\", \"’\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"‘\", \"‘\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"\", \"—\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"“\", \"“\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"”\", \"”\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"–\", \"–\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"—\", \"—\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"…\", \"…\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"™\", \"™\")");
+ pg_query($dbconn, "UPDATE SiteTree SET Content = REPLACE(Content, \"&\", \"&\")");
+
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"×\", \"x\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"’\", \"’\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"‘\", \"‘\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"\", \"—\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"“\", \"“\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"”\", \"”\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"–\", \"–\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"—\", \"—\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"…\", \"…\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"™\", \"™\")");
+ pg_query($dbconn, "UPDATE PageComment SET Comment = REPLACE(Comment, \"&\", \"&\")");
+
+
+ }
+
+ pg_close($dbconn);
+
+ } // end function
+
+} // end class
+?>
diff --git a/0.2.1-rc1/css/archivewidget.css b/0.2.1-rc1/css/archivewidget.css
new file mode 100644
index 0000000..0018f54
--- /dev/null
+++ b/0.2.1-rc1/css/archivewidget.css
@@ -0,0 +1,9 @@
+.archiveMonths{
+
+}
+
+ul.archiveYears li{
+ display: inline;
+ font-size: 1.2em !important;
+ margin:0 !important;
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/css/bbcodehelp.css b/0.2.1-rc1/css/bbcodehelp.css
new file mode 100644
index 0000000..05db9a7
--- /dev/null
+++ b/0.2.1-rc1/css/bbcodehelp.css
@@ -0,0 +1,32 @@
+/*
+ Foundational BBHelper formatting
+*/
+
+ul.bbcodeExamples li {
+ list-style-type:none;
+ font-size: 1em;
+}
+ul.bbcodeExamples li.last {
+ border: none;
+}
+
+ul.bbcodeExamples li span.example {
+
+}
+
+#BBTagsHolder{
+ color: #777;
+ padding: 5px;
+ width: 270px;
+ background-color: #fff;
+ font-size:0.8em;
+}
+
+.bbcodeExamples{
+ margin: 0 !important;
+ padding: 0;
+}
+
+#BBCodeHint{
+ cursor: pointer;
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/css/blog.css b/0.2.1-rc1/css/blog.css
new file mode 100644
index 0000000..691205f
--- /dev/null
+++ b/0.2.1-rc1/css/blog.css
@@ -0,0 +1,11 @@
+.BlogError {
+ text-align: center;
+}
+
+.BlogError p {
+ color: #fff;
+ display: inline;
+ background-color: #f77;
+ padding: 7px;
+ font-weight:bold;
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/css/flickrwidget.css b/0.2.1-rc1/css/flickrwidget.css
new file mode 100644
index 0000000..ef16f0a
--- /dev/null
+++ b/0.2.1-rc1/css/flickrwidget.css
@@ -0,0 +1,3 @@
+div.flickrwidget {
+ text-align: center;
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/css/subscribersswidget.css b/0.2.1-rc1/css/subscribersswidget.css
new file mode 100644
index 0000000..9613397
--- /dev/null
+++ b/0.2.1-rc1/css/subscribersswidget.css
@@ -0,0 +1,4 @@
+.subscribeLink {
+ background: url(../images/feed-icon-14x14.png) no-repeat left center;
+ padding-left: 20px;
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/css/tagcloud.css b/0.2.1-rc1/css/tagcloud.css
new file mode 100644
index 0000000..464952f
--- /dev/null
+++ b/0.2.1-rc1/css/tagcloud.css
@@ -0,0 +1,6 @@
+.tagcloud .not-popular { font-size: 1em; }
+.tagcloud .not-very-popular { font-size: 1.3em; }
+.tagcloud .somewhat-popular { font-size: 1.6em; }
+.tagcloud .popular { font-size: 1.9em; }
+.tagcloud .very-popular { font-size: 2.2em; }
+.tagcloud .ultra-popular { font-size: 2.5em; }
\ No newline at end of file
diff --git a/0.2.1-rc1/images/blogholder-file.gif b/0.2.1-rc1/images/blogholder-file.gif
new file mode 100644
index 0000000..4b5b00f
Binary files /dev/null and b/0.2.1-rc1/images/blogholder-file.gif differ
diff --git a/0.2.1-rc1/images/blogpage-file.gif b/0.2.1-rc1/images/blogpage-file.gif
new file mode 100644
index 0000000..73cc55b
Binary files /dev/null and b/0.2.1-rc1/images/blogpage-file.gif differ
diff --git a/0.2.1-rc1/images/feed-icon-14x14.png b/0.2.1-rc1/images/feed-icon-14x14.png
new file mode 100755
index 0000000..b3c949d
Binary files /dev/null and b/0.2.1-rc1/images/feed-icon-14x14.png differ
diff --git a/0.2.1-rc1/images/feed-icon-28x28.png b/0.2.1-rc1/images/feed-icon-28x28.png
new file mode 100644
index 0000000..d64c669
Binary files /dev/null and b/0.2.1-rc1/images/feed-icon-28x28.png differ
diff --git a/0.2.1-rc1/javascript/bbcodehelp.js b/0.2.1-rc1/javascript/bbcodehelp.js
new file mode 100644
index 0000000..a035b0f
--- /dev/null
+++ b/0.2.1-rc1/javascript/bbcodehelp.js
@@ -0,0 +1,12 @@
+Behaviour.register({
+ '#BBCodeHint': {
+ onclick: function() {
+ if($('BBTagsHolder').style.display == "none") {
+ Effect.BlindDown('BBTagsHolder');
+ } else{
+ Effect.BlindUp('BBTagsHolder');
+ }
+ return false;
+ }
+ }
+});
diff --git a/0.2.1-rc1/lang/_manifest_exclude b/0.2.1-rc1/lang/_manifest_exclude
new file mode 100644
index 0000000..e69de29
diff --git a/0.2.1-rc1/lang/ar_SA.php b/0.2.1-rc1/lang/ar_SA.php
new file mode 100644
index 0000000..b9fc088
--- /dev/null
+++ b/0.2.1-rc1/lang/ar_SA.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/bg_BG.php b/0.2.1-rc1/lang/bg_BG.php
new file mode 100644
index 0000000..72f9d65
--- /dev/null
+++ b/0.2.1-rc1/lang/bg_BG.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/da_DK.php b/0.2.1-rc1/lang/da_DK.php
new file mode 100644
index 0000000..0ce46e3
--- /dev/null
+++ b/0.2.1-rc1/lang/da_DK.php
@@ -0,0 +1,61 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/de_DE.php b/0.2.1-rc1/lang/de_DE.php
new file mode 100644
index 0000000..5803db9
--- /dev/null
+++ b/0.2.1-rc1/lang/de_DE.php
@@ -0,0 +1,61 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/en_GB.php b/0.2.1-rc1/lang/en_GB.php
new file mode 100644
index 0000000..671fb1a
--- /dev/null
+++ b/0.2.1-rc1/lang/en_GB.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/en_US.php b/0.2.1-rc1/lang/en_US.php
new file mode 100644
index 0000000..00a64b6
--- /dev/null
+++ b/0.2.1-rc1/lang/en_US.php
@@ -0,0 +1,119 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/es_419.php b/0.2.1-rc1/lang/es_419.php
new file mode 100644
index 0000000..27d8ebb
--- /dev/null
+++ b/0.2.1-rc1/lang/es_419.php
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/es_ES.php b/0.2.1-rc1/lang/es_ES.php
new file mode 100644
index 0000000..6f2645d
--- /dev/null
+++ b/0.2.1-rc1/lang/es_ES.php
@@ -0,0 +1,39 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/es_MX.php b/0.2.1-rc1/lang/es_MX.php
new file mode 100644
index 0000000..915a34d
--- /dev/null
+++ b/0.2.1-rc1/lang/es_MX.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/et_EE.php b/0.2.1-rc1/lang/et_EE.php
new file mode 100644
index 0000000..c0dd740
--- /dev/null
+++ b/0.2.1-rc1/lang/et_EE.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/fr_FR.php b/0.2.1-rc1/lang/fr_FR.php
new file mode 100644
index 0000000..1f66042
--- /dev/null
+++ b/0.2.1-rc1/lang/fr_FR.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/hr_HR.php b/0.2.1-rc1/lang/hr_HR.php
new file mode 100644
index 0000000..cde02fb
--- /dev/null
+++ b/0.2.1-rc1/lang/hr_HR.php
@@ -0,0 +1,56 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/is_IS.php b/0.2.1-rc1/lang/is_IS.php
new file mode 100644
index 0000000..26e5236
--- /dev/null
+++ b/0.2.1-rc1/lang/is_IS.php
@@ -0,0 +1,63 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/it_IT.php b/0.2.1-rc1/lang/it_IT.php
new file mode 100644
index 0000000..020d24f
--- /dev/null
+++ b/0.2.1-rc1/lang/it_IT.php
@@ -0,0 +1,32 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/nl_NL.php b/0.2.1-rc1/lang/nl_NL.php
new file mode 100644
index 0000000..35afd06
--- /dev/null
+++ b/0.2.1-rc1/lang/nl_NL.php
@@ -0,0 +1,37 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/pl_PL.php b/0.2.1-rc1/lang/pl_PL.php
new file mode 100644
index 0000000..2c6a555
--- /dev/null
+++ b/0.2.1-rc1/lang/pl_PL.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/pt_PT.php b/0.2.1-rc1/lang/pt_PT.php
new file mode 100644
index 0000000..63eae9b
--- /dev/null
+++ b/0.2.1-rc1/lang/pt_PT.php
@@ -0,0 +1,40 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/ru_RU.php b/0.2.1-rc1/lang/ru_RU.php
new file mode 100644
index 0000000..5f4ac94
--- /dev/null
+++ b/0.2.1-rc1/lang/ru_RU.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/sr_RS.php b/0.2.1-rc1/lang/sr_RS.php
new file mode 100644
index 0000000..e11b271
--- /dev/null
+++ b/0.2.1-rc1/lang/sr_RS.php
@@ -0,0 +1,58 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/lang/tr_TR.php b/0.2.1-rc1/lang/tr_TR.php
new file mode 100644
index 0000000..0244791
--- /dev/null
+++ b/0.2.1-rc1/lang/tr_TR.php
@@ -0,0 +1,62 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/ArchiveWidget.ss b/0.2.1-rc1/templates/ArchiveWidget.ss
new file mode 100644
index 0000000..198fc53
--- /dev/null
+++ b/0.2.1-rc1/templates/ArchiveWidget.ss
@@ -0,0 +1,21 @@
+<% if DisplayMode == month %>
+
+<% else %>
+
+<% end_if %>
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/BlogManagementWidget.ss b/0.2.1-rc1/templates/BlogManagementWidget.ss
new file mode 100644
index 0000000..c2e6a6a
--- /dev/null
+++ b/0.2.1-rc1/templates/BlogManagementWidget.ss
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/Includes/BlogPagination.ss b/0.2.1-rc1/templates/Includes/BlogPagination.ss
new file mode 100644
index 0000000..220aab3
--- /dev/null
+++ b/0.2.1-rc1/templates/Includes/BlogPagination.ss
@@ -0,0 +1,27 @@
+<% if BlogEntries.MoreThanOnePage %>
+
+
+ <% if BlogEntries.NotFirstPage %>
+ Prev
+ <% end_if %>
+
+
+ <% control BlogEntries.PaginationSummary(4) %>
+ <% if CurrentBool %>
+ $PageNum
+ <% else %>
+ <% if Link %>
+ $PageNum
+ <% else %>
+ …
+ <% end_if %>
+ <% end_if %>
+ <% end_control %>
+
+
+ <% if BlogEntries.NotLastPage %>
+ Next
+ <% end_if %>
+
+
+<% end_if %>
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/Includes/BlogSideBar.ss b/0.2.1-rc1/templates/Includes/BlogSideBar.ss
new file mode 100644
index 0000000..ebd57aa
--- /dev/null
+++ b/0.2.1-rc1/templates/Includes/BlogSideBar.ss
@@ -0,0 +1,3 @@
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/Includes/BlogSummary.ss b/0.2.1-rc1/templates/Includes/BlogSummary.ss
new file mode 100644
index 0000000..7904a6b
--- /dev/null
+++ b/0.2.1-rc1/templates/Includes/BlogSummary.ss
@@ -0,0 +1,14 @@
+
+
+
<% _t('POSTEDBY', 'Posted by') %> $Author.XML <% _t('POSTEDON', 'on') %> $Date.Long | $Comments.Count <% _t('COMMENTS', 'Comments') %>
+ <% if TagsCollection %>
+
+ Tags:
+ <% control TagsCollection %>
+ $Tag <% if Last %><% else %>,<% end_if %>
+ <% end_control %>
+
+ <% end_if %>
+ $ParagraphSummary
+
| Read the full post
+
diff --git a/0.2.1-rc1/templates/Includes/TrackBacks.ss b/0.2.1-rc1/templates/Includes/TrackBacks.ss
new file mode 100644
index 0000000..77210c5
--- /dev/null
+++ b/0.2.1-rc1/templates/Includes/TrackBacks.ss
@@ -0,0 +1,20 @@
+
+
TrackBacks
+
+ <% if TrackBacks %>
+
+ <% else %>
+
No TrackBacks have been submitted for this page.
+ <% end_if %>
+
+
Trackback URL for this page.
+
+
+
diff --git a/0.2.1-rc1/templates/Layout/BlogEntry.ss b/0.2.1-rc1/templates/Layout/BlogEntry.ss
new file mode 100644
index 0000000..cc37068
--- /dev/null
+++ b/0.2.1-rc1/templates/Layout/BlogEntry.ss
@@ -0,0 +1,30 @@
+<% include BlogSideBar %>
+
+ <% include BreadCrumbs %>
+
+
+
$Title
+
<% _t('POSTEDBY', 'Posted by') %> $Author.XML <% _t('POSTEDON', 'on') %> $Date.Long | $Comments.Count <% _t('COMMENTS', 'Comments') %>
+ <% if TagsCollection %>
+
+ <% _t('TAGS', 'Tags:') %>
+ <% control TagsCollection %>
+ $Tag <% if Last %><% else %>,<% end_if %>
+ <% end_control %>
+
+ <% end_if %>
+ <% if IsWYSIWYGEnabled %>
+ $Content
+ <% else %>
+ $ParsedContent
+ <% end_if %>
+
+
+
+ <% if CurrentMember %>
<% _t('EDITTHIS', 'Edit this post') %> | <% _t('UNPUBLISHTHIS', 'Unpublish this post') %>
<% end_if %>
+
+ <% if TrackBacksEnabled %>
+ <% include TrackBacks %>
+ <% end_if %>
+ $PageComments
+
diff --git a/0.2.1-rc1/templates/Layout/BlogHolder.ss b/0.2.1-rc1/templates/Layout/BlogHolder.ss
new file mode 100644
index 0000000..d0d3eec
--- /dev/null
+++ b/0.2.1-rc1/templates/Layout/BlogHolder.ss
@@ -0,0 +1,21 @@
+<% include BlogSideBar %>
+
+
+
+ <% include BreadCrumbs %>
+
+ <% if Tag %>
+
<% _t('VIEWINGTAGGED', 'Viewing entries tagged with') %> '$Tag'
+ <% end_if %>
+
+ <% if BlogEntries %>
+ <% control BlogEntries %>
+ <% include BlogSummary %>
+ <% end_control %>
+ <% else %>
+ <% _t('NOENTRIES', 'There are no blog entries') %>
+ <% end_if %>
+
+ <% include BlogPagination %>
+
+
diff --git a/0.2.1-rc1/templates/RSSWidget.ss b/0.2.1-rc1/templates/RSSWidget.ss
new file mode 100644
index 0000000..1cf1089
--- /dev/null
+++ b/0.2.1-rc1/templates/RSSWidget.ss
@@ -0,0 +1,7 @@
+
+ <% control FeedItems %>
+
+ $Title
+
+ <% end_control %>
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/SubscribeRSSWidget.ss b/0.2.1-rc1/templates/SubscribeRSSWidget.ss
new file mode 100644
index 0000000..cc29b1e
--- /dev/null
+++ b/0.2.1-rc1/templates/SubscribeRSSWidget.ss
@@ -0,0 +1,5 @@
+
+
+ <% _t('SUBSCRIBETEXT', 'Subscribe') %>
+
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/TagCloudWidget.ss b/0.2.1-rc1/templates/TagCloudWidget.ss
new file mode 100644
index 0000000..773987c
--- /dev/null
+++ b/0.2.1-rc1/templates/TagCloudWidget.ss
@@ -0,0 +1,5 @@
+
+ <% control TagsCollection %>
+ $Tag
+ <% end_control %>
+
\ No newline at end of file
diff --git a/0.2.1-rc1/templates/TrackBackPingReturn.ss b/0.2.1-rc1/templates/TrackBackPingReturn.ss
new file mode 100644
index 0000000..b31f395
--- /dev/null
+++ b/0.2.1-rc1/templates/TrackBackPingReturn.ss
@@ -0,0 +1,5 @@
+
+
+ $Error
+ <% if Message %>$Message <% end_if %>
+
diff --git a/0.2.1-rc1/templates/TrackBackRdf.ss b/0.2.1-rc1/templates/TrackBackRdf.ss
new file mode 100644
index 0000000..62c73b6
--- /dev/null
+++ b/0.2.1-rc1/templates/TrackBackRdf.ss
@@ -0,0 +1,3 @@
+
+
+
diff --git a/0.2.1-rc1/tests/BlogHolderFunctionalTest.php b/0.2.1-rc1/tests/BlogHolderFunctionalTest.php
new file mode 100644
index 0000000..566cf00
--- /dev/null
+++ b/0.2.1-rc1/tests/BlogHolderFunctionalTest.php
@@ -0,0 +1,49 @@
+objFromFixture('BlogHolder', 'blogholder');
+ $blogHolder->publish('Stage', 'LIve');
+ $blogEntry = $this->objFromFixture('BlogEntry', 'entry1');
+ $blogEntry->publish('Stage', 'LIve');
+ }
+
+ function testFrontendBlogPostRequiresPermission() {
+ // get valid SecurityID (from comments form, would usually be copy/pasted)
+ $blogEntry = $this->objFromFixture('BlogEntry', 'entry1');
+ $response = $this->get($blogEntry->URLSegment);
+ $securityID = Session::get('SecurityID');
+
+ // without login
+ $data = array(
+ 'Title'=>'Disallowed',
+ 'Author'=>'Disallowed',
+ 'Content'=>'Disallowed',
+ 'action_postblog' => 'Post blog entry',
+ 'SecurityID' => $securityID
+ );
+ $response = $this->post('blog/BlogEntryForm', $data);
+ $this->assertFalse(DataObject::get_one('BlogEntry', sprintf("Title = 'Disallowed'")));
+
+ // with login
+ $blogEditor = $this->objFromFixture('Member', 'blog_editor');
+ $blogEditor->logIn();
+ $data = array(
+ 'Title'=>'Allowed',
+ 'Author'=>'Allowed',
+ 'Content'=>'Allowed',
+ 'action_postblog' => 'Post blog entry',
+ 'SecurityID' => $securityID
+ );
+ $response = $this->post('blog/BlogEntryForm', $data);
+ $this->assertType('BlogEntry', DataObject::get_one('BlogEntry', sprintf("Title = 'Allowed'")));
+ }
+}
\ No newline at end of file
diff --git a/0.2.1-rc1/tests/BlogHolderFunctionalTest.yml b/0.2.1-rc1/tests/BlogHolderFunctionalTest.yml
new file mode 100644
index 0000000..561c1ba
--- /dev/null
+++ b/0.2.1-rc1/tests/BlogHolderFunctionalTest.yml
@@ -0,0 +1,20 @@
+Permission:
+ blog_management:
+ Code: BLOGMANAGEMENT
+Group:
+ blog_editors:
+ Code: blog-editors
+ Permissions: =>Permission.blog_management
+Member:
+ blog_editor:
+ Email: blogeditor@test.com
+ Groups: =>Group.blog_editors
+BlogHolder:
+ blogholder:
+ Title: Blog Holder
+ URLSegment: blog
+BlogEntry:
+ entry1:
+ Title: Blog Entry
+ ProvideComments: 1
+ Parent: =>BlogHolder.blogholder
\ No newline at end of file
diff --git a/0.2.1-rc1/tests/BlogHolderTest.php b/0.2.1-rc1/tests/BlogHolderTest.php
new file mode 100644
index 0000000..fe2c179
--- /dev/null
+++ b/0.2.1-rc1/tests/BlogHolderTest.php
@@ -0,0 +1,57 @@
+fixture->objFromFixture('BlogHolder', 'mainblog');
+
+ $this->assertEquals($mainblog->Entries()->Count(), 3);
+ }
+
+ function testEntriesByMonth() {
+ $mainblog = $this->fixture->objFromFixture('BlogHolder', 'mainblog');
+
+ $entries = $mainblog->Entries('', '', '2008-01');
+ $this->assertEquals($entries->Count(), 2);
+ $expectedEntries = array(
+ 'test-post-2',
+ 'test-post-3'
+ );
+
+ foreach($entries as $entry) {
+ $this->assertContains($entry->URLSegment, $expectedEntries);
+ }
+ }
+
+ function textEntriesByYear() {
+ $mainblog = $this->fixture->objFromFixture('BlogHolder', 'mainblog');
+
+ $entries = $mainblog->Entries('', '', '2007');
+ $this->assertEquals($entries->Count(), 1);
+ $expectedEntries = array(
+ 'test-post'
+ );
+
+ foreach($entries as $entry) {
+ $this->assertContains($entry->URLSegment, $expectedEntries);
+ }
+ }
+
+ function testEntriesByTag() {
+ $mainblog = $this->fixture->objFromFixture('BlogHolder', 'mainblog');
+
+ $entries = $mainblog->Entries('', 'tag1');
+ $this->assertEquals($entries->Count(), 2);
+ $expectedEntries = array(
+ 'test-post',
+ 'test-post-3'
+ );
+
+ foreach($entries as $entry) {
+ $this->assertContains($entry->URLSegment, $expectedEntries);
+ }
+ }
+}
+
+?>
diff --git a/0.2.1-rc1/tests/BlogTest.yml b/0.2.1-rc1/tests/BlogTest.yml
new file mode 100644
index 0000000..1d49fe6
--- /dev/null
+++ b/0.2.1-rc1/tests/BlogTest.yml
@@ -0,0 +1,28 @@
+BlogHolder:
+ mainblog:
+ Title: Main Blog
+ otherblog:
+ Title: Other Blog
+
+BlogEntry:
+ testpost:
+ Title: Test Post
+ URLSegment: test-post
+ Date: 2007-02-17 18:45:00
+ Parent: =>BlogHolder.mainblog
+ Tags: tag1,tag2
+ testpost2:
+ Title: Test Post 2
+ URLSegment: test-post-2
+ Date: 2008-01-31 20:48:00
+ Parent: =>BlogHolder.mainblog
+ Tags: tag2,tag3
+ testpost3:
+ Title: Test Post 3
+ URLSegment: test-post-3
+ Date: 2008-01-17 18:45:00
+ Parent: =>BlogHolder.mainblog
+ Tags: tag1,tag2,tag3
+
+
+