diff --git a/.gitignore b/.gitignore index a5ffac4..3b7b7c6 100755 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ .ssh/* .bash* .profile -vendor/* +/vendor/* .sass-cache/* +/node_modules/ diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 61b0c9f..de09355 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -6,4 +6,4 @@ checks: duplication: true filter: - paths: [code/*, tests/*] + paths: [src/*, tests/*] diff --git a/.travis.yml b/.travis.yml index 41abb51..f64e3c1 100755 --- a/.travis.yml +++ b/.travis.yml @@ -12,8 +12,6 @@ before_install: env: global: - - DB=MYSQL CORE_RELEASE=3.1 - # Turn coverage off by default, as it's expensive time wise - COVERAGE=0 @@ -21,33 +19,30 @@ env: - MODULE_PATH=blog matrix: - allow_failures: - - php: hhvm-nightly include: - - php: 5.6 - env: DB=MYSQL COVERAGE=1 - - php: 5.5 - env: DB=MYSQL - - php: 5.6 - env: DB=PGSQL - - php: 5.6 - env: DB=MYSQL CORE_RELEASE=3.2 - - php: 5.6 - env: DB=PGSQL CORE_RELEASE=3.2 - - php: 5.4 - env: DB=SQLITE - - php: 5.3 - env: DB=MYSQL - - php: hhvm - env: DB=MYSQL + - php: 7.1 + env: DB=MYSQL CORE_RELEASE=4 COVERAGE=1 + - php: 5.5 + env: DB=MYSQL CORE_RELEASE=4 + + - php: 5.6 + env: DB=MYSQL CORE_RELEASE=4 + - php: 5.6 + env: DB=PGSQL CORE_RELEASE=4 + - php: 5.6 + env: DB=SQLITE CORE_RELEASE=4 + + - php: 7.0 + env: DB=MYSQL CORE_RELEASE=4 before_script: - phpenv rehash - composer self-update || true - - git clone git://github.com/silverstripe-labs/silverstripe-travis-support.git ~/travis-support + - git clone git://github.com/silverstripe/silverstripe-travis-support.git ~/travis-support - php ~/travis-support/travis_setup.php --source `pwd` --target ~/builds/ss --require "silverstripe/comments" --require "silverstripe/widgets" - cd ~/builds/ss + - mv "$MODULE_PATH/phpunit.xml.dist" . #Execute tests with or without coverage script: @@ -57,9 +52,8 @@ script: # Execute tests with coverage. Do this for a small - "if [ \"$COVERAGE\" = \"1\" ]; then vendor/bin/phpunit --coverage-clover=coverage.clover $MODULE_PATH/tests/; fi" -#Upload coverage even if there is a failure -after_script: +# Upload code coverage when tests pass +after_success: - "if [ \"$COVERAGE\" = \"1\" ]; then mv coverage.clover ~/build/$TRAVIS_REPO_SLUG/; fi" - cd ~/build/$TRAVIS_REPO_SLUG - - wget https://scrutinizer-ci.com/ocular.phar - - "if [ \"$COVERAGE\" = \"1\" ]; then travis_retry codecov && travis_retry php ocular.phar code-coverage:upload --format=php-clover coverage.clover; fi" + - "if [ \"$COVERAGE\" = \"1\" ]; then travis_retry codecov; fi" diff --git a/.upgrade.yml b/.upgrade.yml new file mode 100644 index 0000000..813650c --- /dev/null +++ b/.upgrade.yml @@ -0,0 +1,33 @@ +mappings: + GridFieldCategorisationConfig: SilverStripe\Blog\Admin\GridFieldCategorisationConfig + GridFieldFormAction: SilverStripe\Blog\Admin\GridFieldFormAction + GridFieldMergeAction: SilverStripe\Blog\Admin\GridFieldMergeAction + BlogCommentExtension: SilverStripe\Blog\Model\BlogCommentExtension + BlogFilter: SilverStripe\Blog\Model\BlogFilter + BlogFilter_GridField: SilverStripe\Blog\Model\BlogFilter\BlogFilterGridField + BlogMemberExtension: SilverStripe\Blog\Model\BlogMemberExtension + BlogPostFilter: SilverStripe\Blog\Model\BlogPostFilter + BlogPostNotifications: SilverStripe\Blog\Model\BlogPostNotifications + Blog: SilverStripe\Blog\Model\Blog + Blog_Controller: SilverStripe\Blog\Controllers\BlogController + BlogController: SilverStripe\Blog\Controllers\BlogController + BlogCategory: SilverStripe\Blog\Model\BlogCategory + BlogPost: SilverStripe\Blog\Model\BlogPost + BlogPost_Controller: SilverStripe\Blog\Controllers\BlogPostController + BlogPostController: SilverStripe\Blog\Controllers\BlogPostController + BlogTag: SilverStripe\Blog\Model\BlogTag + CategorisationObject: SilverStripe\Blog\Model\CategorisationObject + BlogAdminSidebar: SilverStripe\Blog\Forms\BlogAdminSidebar + GridFieldAddByDBField: SilverStripe\Blog\Forms\GridField\GridFieldAddByDBField + GridFieldBlogPostState: SilverStripe\Blog\Forms\GridField\GridFieldBlogPostState + GridFieldConfig_BlogPost: SilverStripe\Blog\Forms\GridField\GridFieldConfig_BlogPost + BlogArchiveWidget: SilverStripe\Blog\Widgets\BlogArchiveWidget + BlogArchiveWidget_Controller: SilverStripe\Blog\Widgets\BlogArchiveWidgetController + BlogCategoriesWidget: SilverStripe\Blog\Widgets\BlogCategoriesWidget + BlogCategoriesWidget_Controller: SilverStripe\Blog\Widgets\BlogCategoriesWidgetController + BlogRecentPostsWidget: SilverStripe\Blog\Widgets\BlogRecentPostsWidget + BlogRecentPostsWidget_Controller: SilverStripe\Blog\Widgets\BlogRecentPostsWidgetController + BlogTagsCloudWidget: SilverStripe\Blog\Widgets\BlogTagsCloudWidget + BlogTagsCloudWidget_Controller: SilverStripe\Blog\Widgets\BlogTagsCloudWidgetController + BlogTagsWidget: SilverStripe\Blog\Widgets\BlogTagsWidget + BlogTagsWidget_Controller: SilverStripe\Blog\Widgets\BlogTagsWidgetController diff --git a/README.md b/README.md index a612d19..800304f 100755 --- a/README.md +++ b/README.md @@ -10,16 +10,16 @@ ## Documentation -[User guide](docs/en/userguide/index.md) -[Developer documentation](docs/en/index.md) +* [User guide](docs/en/userguide/index.md) +* [Developer documentation](docs/en/index.md) ## Requirements ``` -silverstripe/cms: ^3.1 -silverstripe/lumberjack: ^1.1 -silverstripe/tagfield: ^1.0 +silverstripe/cms: ^4.0 +silverstripe/lumberjack: ^2.0 +silverstripe/tagfield: ^2.0 ``` ### Suggested Modules @@ -35,8 +35,12 @@ silverstripe/comments: * composer require silverstripe/blog ``` -## Upgrading legacy blog to 2.x +## Upgrading + +### Upgrading from 2.x to 3.x + +Aside from the framework and CMS upgrades required the blog module should not require anything extra to be completed. + +### Upgrading legacy blog to 2.x If you're upgrading from blog version 1.0 to 2.x you will need to run the `BlogMigrationTask`. Run the task using `dev/tasks/BlogMigrationTask` either via the browser or sake CLI to migrate your legacy blog to the new version data structure. - - diff --git a/_config.php b/_config.php index d8ae592..aded448 100755 --- a/_config.php +++ b/_config.php @@ -1,8 +1,8 @@ -removeComponentsByType('GridFieldAddNewButton'); - - $this->addComponent( - new GridFieldAddByDBField('buttons-before-left') - ); - - $this->addComponent( - new GridFieldMergeAction($mergeRecords, $parentType, $parentMethod, $childMethod) - ); - - /** - * @var GridFieldDataColumns $columns - */ - $columns = $this->getComponentByType('GridFieldDataColumns'); - - $columns->setFieldFormatting(array( - 'BlogPostsCount' => function ($value, CategorisationObject $item) { - return $item->BlogPosts()->Count(); - } - )); - - $this->changeColumnOrder(); - } - - /** - * Reorders GridField columns so that Actions is last. - */ - protected function changeColumnOrder() - { - /** - * @var GridFieldDataColumns $columns - */ - $columns = $this->getComponentByType('GridFieldDataColumns'); - - $columns->setDisplayFields(array( - 'Title' => 'Title', - 'BlogPostsCount' => 'Posts', - 'MergeAction' => 'MergeAction', - 'Actions' => 'Actions', - )); - } -} diff --git a/code/compat/pages/BlogEntry.php b/code/compat/pages/BlogEntry.php deleted file mode 100644 index 190d475..0000000 --- a/code/compat/pages/BlogEntry.php +++ /dev/null @@ -1,112 +0,0 @@ - 'SS_Datetime', - 'Author' => 'Text', - 'Tags' => 'Text', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) - { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() - { - - //Migrate comma separated tags into BlogTag objects. - foreach ($this->TagNames() as $tag) { - $existingTag = BlogTag::get()->filter(array('Title' => $tag, 'BlogID' => $this->ParentID)); - if ($existingTag->count()) { - //if tag already exists we will simply add it to this post. - $tagObject = $existingTag->First(); - } else { - //if the tag is now we create it and add it to this post. - $tagObject = new BlogTag(); - $tagObject->Title = $tag; - $tagObject->BlogID = $this->ParentID; - $tagObject->write(); - } - - if ($tagObject) { - $this->Tags()->add($tagObject); - } - } - - //Store if the original entity was published or not (draft) - $published = $this->IsPublished(); - // If a user has subclassed BlogEntry, it should not be turned into a BlogPost. - if ($this->ClassName === 'BlogEntry') { - $this->ClassName = 'BlogPost'; - $this->RecordClassName = 'BlogPost'; - } - //Migrate these key data attributes - $this->PublishDate = $this->Date; - $this->AuthorNames = $this->Author; - $this->InheritSideBar = true; - - //Write and additionally publish the item if it was published before. - $this->write(); - if ($published) { - $this->publish('Stage', 'Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } - - /** - * Safely split and parse all distinct tags assigned to this BlogEntry. - * - * @deprecated since version 2.0 - * - * @return array - */ - public function TagNames() - { - $tags = preg_split('/\s*,\s*/', trim($this->Tags)); - - $results = array(); - - foreach ($tags as $tag) { - if ($tag) { - $results[mb_strtolower($tag)] = $tag; - } - } - - return $results; - } -} - -/** - * @deprecated since version 2.0 - */ -class BlogEntry_Controller extends BlogPost_Controller -{ -} diff --git a/code/compat/pages/BlogHolder.php b/code/compat/pages/BlogHolder.php deleted file mode 100644 index 49bc287..0000000 --- a/code/compat/pages/BlogHolder.php +++ /dev/null @@ -1,77 +0,0 @@ - 'Boolean', - 'ShowFullEntry' => 'Boolean', - ); - - /** - * @var array - */ - private static $has_one = array( - 'Owner' => 'Member', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) - { - return false; - } - - - //Overload these to stop the Uncaught Exception: Object->__call(): the method 'parent' does not exist on 'BlogHolder' error. - public function validURLSegment() - { - return true; - } - public function syncLinkTracking() - { - return null; - } - - /** - * {@inheritdoc} - */ - public function up() - { - $published = $this->IsPublished(); - - if ($this->ClassName === 'BlogHolder') { - $this->ClassName = 'Blog'; - $this->RecordClassName = 'Blog'; - $this->PostsPerPage = 10; - $this->write(); - } - - if ($published) { - $this->publish('Stage', 'Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } -} - -/** - * @deprecated since version 2.0 - */ -class BlogHolder_Controller extends BlogTree_Controller -{ -} diff --git a/code/compat/pages/BlogTree.php b/code/compat/pages/BlogTree.php deleted file mode 100644 index 0998de2..0000000 --- a/code/compat/pages/BlogTree.php +++ /dev/null @@ -1,56 +0,0 @@ - 'Varchar(255)', - 'LandingPageFreshness' => 'Varchar', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) - { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() - { - $published = $this->IsPublished(); - if ($this->ClassName === 'BlogTree') { - $this->ClassName = 'Page'; - $this->RecordClassName = 'Page'; - $this->write(); - } - if ($published) { - $this->publish('Stage', 'Live'); - $message = "PUBLISHED: "; - } else { - $message = "DRAFT: "; - } - - return $message . $this->Title; - } -} - -/** - * @deprecated since version 2.0 - */ -class BlogTree_Controller extends Page_Controller -{ -} diff --git a/code/compat/tasks/BlogMigrationTask.php b/code/compat/tasks/BlogMigrationTask.php deleted file mode 100644 index 143fb7c..0000000 --- a/code/compat/tasks/BlogMigrationTask.php +++ /dev/null @@ -1,92 +0,0 @@ -message('Migrating legacy blog records'); - - foreach ($classes as $class) { - $this->upClass($class); - } - } - - /** - * @param string $text - */ - protected function message($text) - { - if (Controller::curr() instanceof DatabaseAdmin) { - DB::alteration_message($text, 'obsolete'); - } else { - echo $text . "
"; - } - } - - /** - * Migrate records of a single class - * - * @param string $class - * @param null|string $stage - */ - protected function upClass($class) - { - if (!class_exists($class)) { - return; - } - - if (is_subclass_of($class, 'SiteTree')) { - $items = SiteTree::get()->filter('ClassName', $class); - } else { - $items = $class::get(); - } - - if ($count = $items->count()) { - $this->message( - sprintf( - 'Migrating %s legacy %s records.', - $count, - $class - ) - ); - - foreach ($items as $item) { - $cancel = $item->extend('onBeforeUp'); - - if ($cancel && min($cancel) === false) { - continue; - } - - /** - * @var MigratableObject $item - */ - $result = $item->up(); - $this->message($result); - - $item->extend('onAfterUp'); - } - } - } - - /** - * {@inheritdoc} - */ - public function down() - { - $this->message('BlogMigrationTask::down() not implemented'); - } -} diff --git a/code/compat/tasks/MigratableObject.php b/code/compat/tasks/MigratableObject.php deleted file mode 100644 index 2c747a8..0000000 --- a/code/compat/tasks/MigratableObject.php +++ /dev/null @@ -1,9 +0,0 @@ - 'Varchar', - ); - - /** - * @var array - */ - private static $only_available_in = array( - 'none', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) - { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() - { - if ($this->DisplayMode) { - $this->ArchiveType = 'Monthly'; - - if ($this->DisplayMode === 'year') { - $this->ArchiveType = 'Yearly'; - } - } - - $this->ClassName = 'BlogArchiveWidget'; - $this->write(); - return "Migrated " . $this->ArchiveType . " archive widget"; - } -} diff --git a/code/compat/widgets/TagCloudWidget.php b/code/compat/widgets/TagCloudWidget.php deleted file mode 100644 index a527ddd..0000000 --- a/code/compat/widgets/TagCloudWidget.php +++ /dev/null @@ -1,47 +0,0 @@ - 'Varchar', - 'Limit' => 'Int', - 'Sortby' => 'Varchar', - ); - - /** - * @var array - */ - private static $only_available_in = array( - 'none', - ); - - /** - * {@inheritdoc} - */ - public function canCreate($member = null) - { - return false; - } - - /** - * {@inheritdoc} - */ - public function up() - { - $this->ClassName = 'BlogTagsWidget'; - $this->write(); - return "Migrated " . $this->Title . " widget"; - } -} diff --git a/code/extensions/BlogPostFilter.php b/code/extensions/BlogPostFilter.php deleted file mode 100644 index 1795bc8..0000000 --- a/code/extensions/BlogPostFilter.php +++ /dev/null @@ -1,45 +0,0 @@ -addWhere(sprintf('"PublishDate" < \'%s\'', Convert::raw2sql(SS_Datetime::now()))); - } - } - - /** - * This is a fix so that when we try to fetch subclasses of BlogPost, lazy loading includes the - * BlogPost table in its query. Leaving this table out means the default sort order column - * PublishDate causes an error. - * - * @see https://github.com/silverstripe/silverstripe-framework/issues/1682 - * - * @param SQLQuery $query - * @param mixed $dataQuery - * @param mixed $parent - */ - public function augmentLoadLazyFields(SQLQuery &$query, &$dataQuery, $parent) - { - $dataQuery->innerJoin('BlogPost', '"SiteTree"."ID" = "BlogPost"."ID"'); - } -} diff --git a/code/extensions/URLSegmentExtension.php b/code/extensions/URLSegmentExtension.php deleted file mode 100644 index 2daba19..0000000 --- a/code/extensions/URLSegmentExtension.php +++ /dev/null @@ -1,78 +0,0 @@ - 'Varchar(255)', - ); - - /** - * {@inheritdoc} - */ - public function onBeforeWrite() - { - if ($this->owner->BlogID) { - $this->owner->generateURLSegment(); - } - } - - /** - * Generates a unique URLSegment from the title. - * - * @param int $increment - * - * @return string - */ - public function generateURLSegment($increment = null) - { - $filter = new URLSegmentFilter(); - - // Setting this to on. Because of the UI flow, it would be quite a lot of work - // to support turning this off. (ie. the add by title flow would not work). - // If this becomes a problem we can approach it then. - // @see https://github.com/silverstripe/silverstripe-blog/issues/376 - $filter->setAllowMultibyte(true); - - $this->owner->URLSegment = $filter->filter($this->owner->Title); - - if (is_int($increment)) { - $this->owner->URLSegment .= '-' . $increment; - } - - // Postgres use '' instead of 0 as an emtpy blog ID - // Without this all the tests fail - if (!$this->owner->BlogID) { - $this->owner->BlogID = 0; - } - - $duplicate = DataList::create($this->owner->ClassName)->filter(array( - 'URLSegment' => $this->owner->URLSegment, - 'BlogID' => $this->owner->BlogID, - )); - - if ($this->owner->ID) { - $duplicate = $duplicate->exclude('ID', $this->owner->ID); - } - - if ($duplicate->count() > 0) { - if (is_int($increment)) { - $increment += 1; - } else { - $increment = 0; - } - - $this->owner->generateURLSegment((int) $increment); - } - - return $this->owner->URLSegment; - } -} diff --git a/code/model/BlogCategory.php b/code/model/BlogCategory.php deleted file mode 100644 index a678e40..0000000 --- a/code/model/BlogCategory.php +++ /dev/null @@ -1,188 +0,0 @@ - 'Varchar(255)', - ); - - /** - * @var array - */ - private static $has_one = array( - 'Blog' => 'Blog', - ); - - /** - * @var array - */ - private static $belongs_many_many = array( - 'BlogPosts' => 'BlogPost', - ); - - /** - * @var array - */ - private static $extensions = array( - 'URLSegmentExtension', - ); - - /** - * @return DataList - */ - public function BlogPosts() - { - $blogPosts = parent::BlogPosts(); - - $this->extend("updateGetBlogPosts", $blogPosts); - - return $blogPosts; - } - - /** - * {@inheritdoc} - */ - public function getCMSFields() - { - $fields = new FieldList( - TextField::create('Title', _t('BlogCategory.Title', 'Title')) - ); - - $this->extend('updateCMSFields', $fields); - - return $fields; - } - - /** - * {@inheritdoc} - */ - protected function validate() - { - $validation = parent::validate(); - if($validation->valid()) { - // Check for duplicate categories - $blog = $this->Blog(); - if($blog && $blog->exists()) { - $existing = $blog->Categories()->filter('Title', $this->Title); - if($this->ID) { - $existing = $existing->exclude('ID', $this->ID); - } - if($existing->count() > 0) { - $validation->error(_t( - 'BlogCategory.Duplicate', - 'A blog category already exists with that name' - ), BlogCategory::DUPLICATE_EXCEPTION); - } - } - } - return $validation; - } - - /** - * Returns a relative link to this category. - * - * @return string - */ - public function getLink() - { - return Controller::join_links($this->Blog()->Link(), 'category', $this->URLSegment); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canView($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canView($member); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canCreate($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - $permission = Blog::config()->grant_user_permission; - - return Permission::checkMember($member, $permission); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canDelete($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canEdit($member); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canEdit($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canEdit($member); - } -} diff --git a/code/model/BlogTag.php b/code/model/BlogTag.php deleted file mode 100644 index 47c9e57..0000000 --- a/code/model/BlogTag.php +++ /dev/null @@ -1,189 +0,0 @@ - 'Varchar(255)', - ); - - /** - * @var array - */ - private static $has_one = array( - 'Blog' => 'Blog', - ); - - /** - * @var array - */ - private static $belongs_many_many = array( - 'BlogPosts' => 'BlogPost', - ); - - /** - * @var array - */ - private static $extensions = array( - 'URLSegmentExtension', - ); - - /** - * @return DataList - */ - public function BlogPosts() - { - $blogPosts = parent::BlogPosts(); - - $this->extend("updateGetBlogPosts", $blogPosts); - - return $blogPosts; - } - - /** - * {@inheritdoc} - */ - public function getCMSFields() - { - $fields = new FieldList( - TextField::create('Title', _t('BlogTag.Title', 'Title')) - ); - - $this->extend('updateCMSFields', $fields); - - return $fields; - } - - /** - * {@inheritdoc} - */ - protected function validate() - { - $validation = parent::validate(); - if($validation->valid()) { - // Check for duplicate tags - $blog = $this->Blog(); - if($blog && $blog->exists()) { - $existing = $blog->Tags()->filter('Title', $this->Title); - if($this->ID) { - $existing = $existing->exclude('ID', $this->ID); - } - if($existing->count() > 0) { - $validation->error(_t( - 'BlogTag.Duplicate', - 'A blog tags already exists with that name' - ), BlogTag::DUPLICATE_EXCEPTION); - } - } - } - return $validation; - } - - /** - * Returns a relative URL for the tag link. - * - * @return string - */ - public function getLink() - { - return Controller::join_links($this->Blog()->Link(), 'tag', $this->URLSegment); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canView($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canView($member); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canCreate($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - $permission = Blog::config()->grant_user_permission; - - return Permission::checkMember($member, $permission); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canDelete($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canEdit($member); - } - - /** - * Inherits from the parent blog or can be overwritten using a DataExtension. - * - * @param null|Member $member - * - * @return bool - */ - public function canEdit($member = null) - { - $extended = $this->extendedCan(__FUNCTION__, $member); - - if ($extended !== null) { - return $extended; - } - - return $this->Blog()->canEdit($member); - } -} diff --git a/code/widgets/BlogCategoriesWidget.php b/code/widgets/BlogCategoriesWidget.php deleted file mode 100644 index 1429dab..0000000 --- a/code/widgets/BlogCategoriesWidget.php +++ /dev/null @@ -1,100 +0,0 @@ - 'Int', - 'Order' => 'Varchar', - 'Direction' => 'Varchar', - ); - - /** - * @var array - */ - private static $has_one = array( - 'Blog' => 'Blog', - ); - - /** - * {@inheritdoc} - */ - public function getCMSFields() - { - $this->beforeUpdateCMSFields(function (FieldList $fields) { - $fields[] = DropdownField::create( - 'BlogID', _t('BlogCategoriesWidget.Blog', 'Blog'), Blog::get()->map() - ); - - $fields[] = NumericField::create( - 'Limit', _t('BlogCategoriesWidget.Limit.Label', 'Limit'), 0 - ) - ->setDescription(_t('BlogCategoriesWidget.Limit.Description', 'Limit the number of categories shown by this widget (set to 0 to show all categories).')) - ->setMaxLength(3); - - $fields[] = DropdownField::create( - 'Order', _t('BlogCategoriesWidget.Sort.Label', 'Sort'), array('Title' => 'Title', 'Created' => 'Created', 'LastEdited' => 'Updated') - ) - ->setDescription(_t('BlogCategoriesWidget.Sort.Description', 'Change the order of categories shown by this widget.')); - - $fields[] = DropdownField::create( - 'Direction', _t('BlogCategoriesWidget.Direction.Label', 'Direction'), array('ASC' => 'Ascending', 'DESC' => 'Descending') - ) - ->setDescription(_t('BlogCategoriesWidget.Direction.Description', 'Change the direction of ordering of categories shown by this widget.')); - }); - - return parent::getCMSFields(); - } - - /** - * @return DataList - */ - public function getCategories() - { - $blog = $this->Blog(); - - if (!$blog) { - return array(); - } - - $query = $blog->Categories(); - - if ($this->Limit) { - $query = $query->limit(Convert::raw2sql($this->Limit)); - } - - if ($this->Order && $this->Direction) { - $query = $query->sort(Convert::raw2sql($this->Order), Convert::raw2sql($this->Direction)); - } - - return $query; - } -} - -class BlogCategoriesWidget_Controller extends Widget_Controller -{ -} diff --git a/composer.json b/composer.json index fe52322..dbaf739 100755 --- a/composer.json +++ b/composer.json @@ -1,39 +1,45 @@ { - "name": "silverstripe/blog", - "description": "A fresh take on blogging in Silverstripe set out to tackle the issue of a cluttered Site Tree.", - "keywords": [ - "silverstripe", - "blog", - "news" - ], - "type": "silverstripe-module", - "require": { - "silverstripe/cms": "^3.1.0", - "silverstripe/lumberjack": "~1.1", - "silverstripe/tagfield": "^1.0" - }, - "require-dev": { - "phpunit/PHPUnit": "~3.7@stable" - }, - "extra": { - "branch-alias": { - "dev-master": "3.x-dev" - } - }, - "license": "BSD-2-Clause", - "authors": [ - { - "name": "Michael Strong", - "email": "github@michaelstrong.co.uk" - } - ], - "suggest": { - "silverstripe/widgets": "Some widgets come with the blog which are compatible with the widgets module.", - "silverstripe/comments": "This module adds comments to your blog." - }, - "replace": { - "micmania1/silverstripe-blog": "*" - }, - "minimum-stability": "dev", - "prefer-stable": true + "name": "silverstripe/blog", + "description": "A fresh take on blogging in Silverstripe set out to tackle the issue of a cluttered Site Tree.", + "keywords": [ + "silverstripe", + "blog", + "news" + ], + "type": "silverstripe-module", + "require": { + "silverstripe/cms": "^4.0", + "silverstripe/lumberjack": "^2.0", + "silverstripe/tagfield": "^2.0" + }, + "require-dev": { + "phpunit/PHPUnit": "~4.8" + }, + "extra": { + "branch-alias": { + "dev-master": "3.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "SilverStripe\\Blog\\": "src/", + "SilverStripe\\Blog\\Tests\\": "tests/" + } + }, + "license": "BSD-2-Clause", + "authors": [ + { + "name": "Michael Strong", + "email": "github@michaelstrong.co.uk" + } + ], + "suggest": { + "silverstripe/widgets": "Some widgets come with the blog which are compatible with the widgets module.", + "silverstripe/comments": "This module adds comments to your blog." + }, + "replace": { + "micmania1/silverstripe-blog": "*" + }, + "minimum-stability": "dev", + "prefer-stable": true } diff --git a/config.rb b/config.rb deleted file mode 100755 index 31c7f33..0000000 --- a/config.rb +++ /dev/null @@ -1,23 +0,0 @@ -# Require any additional compass plugins here. - -# Set this to the root of your project when deployed: -http_path = "/" -css_dir = "css" -sass_dir = "scss" -javascripts_dir = "javascript" - -# You can select your preferred output style here (can be overridden via the command line): -# output_style = :expanded or :nested or :compact or :compressed - -# To enable relative paths to assets via compass helper functions. Uncomment: -relative_assets = true - -# To disable debugging comments that display the original location of your selectors. Uncomment: -line_comments = false - - -# If you prefer the indented syntax, you might want to regenerate this -# project again passing --syntax sass, or you can uncomment this: -# preferred_syntax = :sass -# and then run: -# sass-convert -R --from scss --to sass scss scss && rm -rf sass && mv scss sass diff --git a/css/blog.css b/css/blog.css index fe370bc..a89a5eb 100644 --- a/css/blog.css +++ b/css/blog.css @@ -1,58 +1,43 @@ .no-sidebar .content-container.size3of4 { - width: 75%; -} + width: 75%; } .blog-entry .post-image img { - width: 98.75%; -} + width: 98.75%; } .blog-sidebar .WidgetHolder ul { - margin-left: 0; -} -.blog-sidebar .WidgetHolder ul li { - list-style-type: none; -} + margin-left: 0; } + .blog-sidebar .WidgetHolder ul li { + list-style-type: none; } + ul.blogTagCloud { list-style-type: none; - clear: both; -} -ul.blogTagCloud li { - float: left; - display: inline; - padding-right: 8px; -} -ul.blogTagCloud li a span { + clear: both; } + ul.blogTagCloud li { float: left; - line-height: 30px; text-align: center; - padding: 0px; -} -ul.blogTagCloud .tagCount10 { - font-size: 26pt; -} -ul.blogTagCloud .tagCount9 { - font-size: 24pt; -} -ul.blogTagCloud .tagCount8 { - font-size: 22pt; -} -ul.blogTagCloud .tagCount7 { - font-size: 20pt; -} -ul.blogTagCloud .tagCount6 { - font-size: 18pt; -} -ul.blogTagCloud .tagCount5 { - font-size: 16pt; -} -ul.blogTagCloud .tagCount4 { - font-size: 14pt; -} -ul.blogTagCloud .tagCount3 { - font-size: 12pt; -} -ul.blogTagCloud .tagCount2 { - font-size: 10pt; -} -ul.blogTagCloud .tagCount1 { - font-size: 8pt; -} + display: inline; + padding-right: 8px; } + ul.blogTagCloud li a span { + float: left; + line-height: 30px; + text-align: center; + padding: 0px; } + ul.blogTagCloud .tagCount10 { + font-size: 26pt; } + ul.blogTagCloud .tagCount9 { + font-size: 24pt; } + ul.blogTagCloud .tagCount8 { + font-size: 22pt; } + ul.blogTagCloud .tagCount7 { + font-size: 20pt; } + ul.blogTagCloud .tagCount6 { + font-size: 18pt; } + ul.blogTagCloud .tagCount5 { + font-size: 16pt; } + ul.blogTagCloud .tagCount4 { + font-size: 14pt; } + ul.blogTagCloud .tagCount3 { + font-size: 12pt; } + ul.blogTagCloud .tagCount2 { + font-size: 10pt; } + ul.blogTagCloud .tagCount1 { + font-size: 8pt; } diff --git a/css/cms.css b/css/cms.css index df0da49..7aa1caf 100755 --- a/css/cms.css +++ b/css/cms.css @@ -7,81 +7,66 @@ /* * Sprite maps & Icons */ -.blog-icon-sprite, .gridfield-icon .blog-icon-timer { - background-image: url('../images/blog-icon-s0a5ab5f851.png'); - background-repeat: no-repeat; -} - -.gridfield-icon .blog-icon-timer { - background-position: 0 0; -} +.gridfield-icon.blog-icon-timer { + background: url("../images/blog-icon/timer.png") center no-repeat; } #FeaturedImage .middleColumn { clear: none; - float: left; -} + float: left; } -.blog-admin-sidebar { +.has-panel .cms-content-tools.blog-admin-sidebar { width: 280px; border-right: none; border-left: 1px solid #C0C0C2; - position: absolute; + position: absolute !important; + /* overrides cms !imporant style */ right: 0px; - bottom: 0px; - height: 100%; -} -.blog-admin-sidebar .cms-panel-toggle a { - text-align: left; -} -.blog-admin-sidebar ~ .blog-admin-outer { - width: 100%; - padding-right: 280px; - position: absolute; - height: 100%; - overflow-y: hidden; - overflow-x: hidden; - box-sizing: border-box; -} -.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset { - position: relative; - overflow: auto; - height: 100%; - width: 100%; -} -.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title label { - float: none; -} -.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title .middleColumn, .blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title input { - width: 100%; - max-width: 100%; - margin-left: 0; -} -.blog-admin-sidebar .cms-content-view > .field + .field { - margin-top: 10px; -} -.blog-admin-sidebar .cms-content-view > .field.urlsegment .preview { - padding-top: 0; - line-height: 25px; -} -.blog-admin-sidebar .cms-content-view > .field.urlsegment .edit { - float: right; -} -.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn > .date { - width: 60%; -} -.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn > .time { - width: 36%; - float: right; -} -.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn .middleColumn, .blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn input { - width: 100%; -} -.blog-admin-sidebar.collapsed ~ .blog-admin-outer { - padding-right: 41px; -} -.blog-admin-sidebar.cms-content-tools .cms-panel-content { - width: auto; -} + top: 0; + height: 100%; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-panel-toggle a { + text-align: left; + margin: 0; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-panel-toggle.south { + border-top: 1px solid #aaaaaa; } + .has-panel .cms-content-tools.blog-admin-sidebar ~ .blog-admin-outer { + width: 100%; + padding-right: 280px; + position: absolute; + height: 100%; + overflow-y: hidden; + overflow-x: hidden; + box-sizing: border-box; } + .has-panel .cms-content-tools.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset { + position: relative; + overflow: auto; + height: 100%; + width: 100%; } + .has-panel .cms-content-tools.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title label { + float: none; } + .has-panel .cms-content-tools.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title .middleColumn, .has-panel .cms-content-tools.blog-admin-sidebar ~ .blog-admin-outer > .ss-tabset #Title input { + width: 100%; + max-width: 100%; + margin-left: 0; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field + .field { + margin-top: 10px; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.urlsegment .preview { + padding-top: 0; + line-height: 25px; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.urlsegment .edit { + float: right; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn > .date { + width: 60%; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn > .time { + width: 36%; + float: right; } + .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn .middleColumn, .has-panel .cms-content-tools.blog-admin-sidebar .cms-content-view > .field.datetime > .middleColumn input { + width: 100%; } + .has-panel .cms-content-tools.blog-admin-sidebar.collapsed ~ .blog-admin-outer { + padding-right: 41px; } + .has-panel .cms-content-tools.blog-admin-sidebar.collapsed ~ .blog-admin-outer #Root_Main { + margin-right: 15px; } + .has-panel .cms-content-tools.blog-admin-sidebar.cms-content-tools .cms-panel-content { + width: auto; } .toggle-description { text-indent: -1000000px; @@ -89,63 +74,59 @@ background: url("../images/information.png") no-repeat center center; width: 20px; height: 20px; - margin-left: 4px; -} + margin-left: 4px; } .middleColumn.toggle-description-correct-middle { margin-left: 0; float: left; - width: 416px; -} + width: 416px; } label.right.toggle-description-correct-right { display: inline-block; margin-left: 0; clear: none; - float: left; -} + float: left; } .description.toggle-description-correct-description { width: 416px; - padding: 12px 0; -} + padding: 12px 0; } .custom-summary .ui-accordion-content .field { - margin: 0; -} + margin: 0; } + .custom-summary .ui-accordion-content, .custom-summary .ui-accordion-content .field { - padding: 0; -} + padding: 0; } + .custom-summary .ui-icon-triangle-1-e { - background-position: -16px -128px; -} + background-position: -16px -128px; } .cms table.ss-gridfield-table tr td.MergeAction { - width: 225px; -} -.cms table.ss-gridfield-table tr td.MergeAction a { - display: block; - height: 100%; - width: 100%; -} -.cms table.ss-gridfield-table tr td.MergeAction select { - width: 150px; -} + width: 225px; } + .cms table.ss-gridfield-table tr td.MergeAction a { + display: block; + height: 100%; + width: 100%; } + .cms table.ss-gridfield-table tr td.MergeAction select { + width: 150px; } .cms-content-actions, .cms-preview-controls { - z-index: 999; -} + z-index: 999; } .blog-cms-categorisation .MergeActionReveal { - margin-left: 10px; -} + margin-left: 10px; } + +.blog-cms-categorisation .toolbar--content { + margin-top: 0; } + .blog-cms-categorisation .MergeActionReveal:after { content: ''; background: url("../images/move-icon.png"); display: inline-block; height: 16px; width: 16px; - margin-left: 4px; -} + margin-left: 4px; } + +.blog-cms-categorisation button.action { + margin-left: 5px; } diff --git a/gulpfile.js b/gulpfile.js new file mode 100644 index 0000000..4aa2f74 --- /dev/null +++ b/gulpfile.js @@ -0,0 +1,17 @@ +var sass = require("gulp-sass"); +var gulp = require("gulp"); +var watch = require('gulp-watch'); + +gulp.task("scss", function () { + gulp.src("./scss/*.scss") + .pipe(sass().on('error', sass.logError)) + .pipe(gulp.dest("./css")); +}); + +gulp.task('watch', ['scss'], function() { + gulp.watch('./scss/*.scss', ['scss']); +}); + +gulp.task('default', ['scss'], function() { + // noop +}); diff --git a/js/cms.js b/js/cms.js index 6dbefdf..36fb506 100644 --- a/js/cms.js +++ b/js/cms.js @@ -113,6 +113,10 @@ if(!this.hasClass('collapsed') && ($(".blog-admin-outer").width() < this.getMinInnerWidth())) { this.collapsePanel(); } + + window.onresize = function() { + this.updateLayout(); + }.bind(this); }, togglePanel: function(bool, silent) { this._super(bool, silent); @@ -124,9 +128,16 @@ * @returns {undefined} */ updateLayout: function() { + $(this).css('height', '100%'); + var currentHeight = $(this).outerHeight(); + var bottomHeight = $('.cms-content-actions').eq(0).outerHeight(); + $(this).css('height', (currentHeight - bottomHeight) + "px"); + $(this).css('bottom', bottomHeight + "px"); + $('.cms-container').updateLayoutOptions({ minContentWidth: 820 + this.width() }); + } }); diff --git a/js/gridfieldaddbydbfield.js b/js/gridfieldaddbydbfield.js index f6d31e2..67799f3 100644 --- a/js/gridfieldaddbydbfield.js +++ b/js/gridfieldaddbydbfield.js @@ -1,20 +1,16 @@ (function ($) { - $.entwine('ss', function ($) { - /** * Prevent the CMS hijacking the return key */ $('.add-existing-autocompleter input.text').entwine({ 'onkeydown': function (e) { - if(e.which == 13) { + if (e.which == 13) { $parent = $(this).parents('.add-existing-autocompleter'); $parent.find('button[type="submit"]').click(); return false; } } }); - }); - })(jQuery); diff --git a/lang/en.yml b/lang/en.yml index 9ba7ddd..afffaf8 100755 --- a/lang/en.yml +++ b/lang/en.yml @@ -87,7 +87,7 @@ en: PLURALNAME: 'Blog Recent Posts Widgets' SINGULARNAME: 'Blog Recent Posts Widget' BlogTag: - Duplicate: 'A blog tags already exists with that name' + Duplicate: 'A blog tag already exists with that name' PLURALNAME: 'Blog Tags' SINGULARNAME: 'Blog Tag' Title: Title diff --git a/package.json b/package.json new file mode 100644 index 0000000..4fe994b --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "silverstripe-blog", + "version": "3.0.0", + "description": "Silverstripe blog module", + "main": "index.js", + "directories": { + "doc": "docs", + "test": "tests" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/silverstripe/silverstripe-blog.git" + }, + "author": "", + "license": "BSD-3", + "bugs": { + "url": "https://github.com/silverstripe/silverstripe-blog/issues" + }, + "homepage": "https://github.com/silverstripe/silverstripe-blog#readme", + "devDependencies": { + "gulp": "^3.9.1", + "gulp-sass": "^2.3.1", + "gulp-watch": "^4.3.6" + } +} diff --git a/phpunit.xml.dist b/phpunit.xml.dist new file mode 100644 index 0000000..f7339cb --- /dev/null +++ b/phpunit.xml.dist @@ -0,0 +1,18 @@ + + + blog/tests + + + + + + + + + blog/src/ + + blog/tests/ + + + + diff --git a/scss/cms.scss b/scss/cms.scss index 42dcea6..c2a813e 100755 --- a/scss/cms.scss +++ b/scss/cms.scss @@ -1,191 +1,208 @@ -/** - * CMS Styles - */ -/** - * Include Compass framework - */ -@import "compass"; -/* - * Sprite maps & Icons - */ -@import "compass/utilities/sprites/base"; -@import "blog-icon/*.png"; -// buttons - -.gridfield-icon { - @include all-blog-icon-sprites; -} - -#FeaturedImage .middleColumn { - clear: none; - float: left; -} - -.blog-admin-sidebar { - width: 280px; - border-right: none; - border-left: 1px solid #C0C0C2; - position: absolute; - right: 0px; - bottom: 0px; - height: 100%; - - .cms-panel-toggle a { - text-align: left; - } - - ~ .blog-admin-outer { - width: 100%; - padding-right: 280px; - position: absolute; - height: 100%; - overflow-y: hidden; - overflow-x: hidden; - box-sizing: border-box; - - > .ss-tabset { - position: relative; - overflow: auto; - height: 100%; - width: 100%; - - #Title { - label { - float: none; - } - .middleColumn, input { - width: 100%; - max-width: 100%; - margin-left: 0; - } - } - } - } - - .cms-content-view { - > .field { - + .field { - margin-top: 10px; - } - - &.urlsegment { - .preview { - padding-top: 0; - line-height: 25px; - } - - .edit { - float: right; - } - } - - &.datetime { - > .middleColumn { - > .date { - width: 60%; - } - - > .time { - width: 36%; - float: right; - } - - .middleColumn, input { - width: 100%; - } - } - } - } - } - - &.collapsed { - ~ .blog-admin-outer { - padding-right: 41px; - } - } - - &.cms-content-tools { - .cms-panel-content { - width: auto; - } - } -} - -.toggle-description { - text-indent: -1000000px; - display: inline-block; - background: url("../images/information.png") no-repeat center center; - width: 20px; - height: 20px; - margin-left: 4px; -} - -.middleColumn.toggle-description-correct-middle { - margin-left: 0; - float: left; - width: 416px; -} - -label.right.toggle-description-correct-right { - display: inline-block; - margin-left: 0; - clear: none; - float: left; -} - -.description.toggle-description-correct-description { - width: 416px; - padding: 12px 0; -} - -.custom-summary { - .ui-accordion-content .field { - margin: 0; - } - - .ui-accordion-content, - .ui-accordion-content .field { - padding: 0; - } - - .ui-icon-triangle-1-e { - background-position: -16px -128px; - } -} - -.cms table.ss-gridfield-table { - tr td.MergeAction { - width: 225px; - - a { - display: block; - height: 100%; - width: 100%; - } - - select { - width: 150px; - } - } -} - -.cms-content-actions, -.cms-preview-controls { - z-index: 999; -} - -.blog-cms-categorisation { - - .MergeActionReveal { - margin-left: 10px; - } - - .MergeActionReveal:after { - content: ''; - background: url('../images/move-icon.png'); - display: inline-block; - height: 16px; - width: 16px; - margin-left: 4px; - } -} +/** + * CMS Styles + */ +/** + * Include Compass framework + */ +// @import "compass"; +/* + * Sprite maps & Icons + */ +// @import "compass/utilities/sprites/base"; +// @import "blog-icon/*.png"; +// buttons + +.gridfield-icon.blog-icon-timer { + background: url('../images/blog-icon/timer.png') center no-repeat; +} + +#FeaturedImage .middleColumn { + clear: none; + float: left; +} + +.has-panel .cms-content-tools.blog-admin-sidebar { + width: 280px; + border-right: none; + border-left: 1px solid #C0C0C2; + position: absolute !important; /* overrides cms !imporant style */ + right: 0px; + top: 0; + height: 100%; + + .cms-panel-toggle a { + text-align: left; + margin: 0; + } + + .cms-panel-toggle.south { + border-top: 1px solid #aaaaaa; + } + + ~ .blog-admin-outer { + width: 100%; + padding-right: 280px; + position: absolute; + height: 100%; + overflow-y: hidden; + overflow-x: hidden; + box-sizing: border-box; + + > .ss-tabset { + position: relative; + overflow: auto; + height: 100%; + width: 100%; + + #Title { + label { + float: none; + } + .middleColumn, input { + width: 100%; + max-width: 100%; + margin-left: 0; + } + } + } + } + + .cms-content-view { + > .field { + + .field { + margin-top: 10px; + } + + &.urlsegment { + .preview { + padding-top: 0; + line-height: 25px; + } + + .edit { + float: right; + } + } + + &.datetime { + > .middleColumn { + > .date { + width: 60%; + } + + > .time { + width: 36%; + float: right; + } + + .middleColumn, input { + width: 100%; + } + } + } + } + } + + &.collapsed { + ~ .blog-admin-outer { + padding-right: 41px; + + #Root_Main { + margin-right: 15px; + } + } + + } + + &.cms-content-tools { + .cms-panel-content { + width: auto; + } + } +} + +.toggle-description { + text-indent: -1000000px; + display: inline-block; + background: url("../images/information.png") no-repeat center center; + width: 20px; + height: 20px; + margin-left: 4px; +} + +.middleColumn.toggle-description-correct-middle { + margin-left: 0; + float: left; + width: 416px; +} + +label.right.toggle-description-correct-right { + display: inline-block; + margin-left: 0; + clear: none; + float: left; +} + +.description.toggle-description-correct-description { + width: 416px; + padding: 12px 0; +} + +.custom-summary { + .ui-accordion-content .field { + margin: 0; + } + + .ui-accordion-content, + .ui-accordion-content .field { + padding: 0; + } + + .ui-icon-triangle-1-e { + background-position: -16px -128px; + } +} + +.cms table.ss-gridfield-table { + tr td.MergeAction { + width: 225px; + + a { + display: block; + height: 100%; + width: 100%; + } + + select { + width: 150px; + } + } +} + +.cms-content-actions, +.cms-preview-controls { + z-index: 999; +} + +.blog-cms-categorisation { + .MergeActionReveal { + margin-left: 10px; + } + + .toolbar--content { + margin-top: 0; + } + + .MergeActionReveal:after { + content: ''; + background: url('../images/move-icon.png'); + display: inline-block; + height: 16px; + width: 16px; + margin-left: 4px; + } + + button.action { + margin-left: 5px; + } +} diff --git a/src/Admin/GridFieldCategorisationConfig.php b/src/Admin/GridFieldCategorisationConfig.php new file mode 100644 index 0000000..697b0f8 --- /dev/null +++ b/src/Admin/GridFieldCategorisationConfig.php @@ -0,0 +1,68 @@ +removeComponentsByType('SilverStripe\\Forms\\GridField\\GridFieldAddNewButton'); + + $this->addComponent( + new GridFieldAddByDBField('buttons-before-left') + ); + + $this->addComponent( + new GridFieldMergeAction($mergeRecords, $parentType, $parentMethod, $childMethod) + ); + + /** + * @var GridFieldDataColumns $columns + */ + $columns = $this->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns'); + + $columns->setFieldFormatting( + array( + 'BlogPostsCount' => function ($value, CategorisationObject $item) { + return $item->BlogPosts()->Count(); + } + ) + ); + + $this->changeColumnOrder(); + } + + /** + * Reorders GridField columns so that Actions is last. + */ + protected function changeColumnOrder() + { + /** + * @var GridFieldDataColumns $columns + */ + $columns = $this->getComponentByType('SilverStripe\\Forms\\GridField\\GridFieldDataColumns'); + + $columns->setDisplayFields( + array( + 'Title' => 'Title', + 'BlogPostsCount' => 'Posts', + 'MergeAction' => 'MergeAction', + 'Actions' => 'Actions' + ) + ); + } +} diff --git a/code/admin/GridFieldFormAction.php b/src/Admin/GridFieldFormAction.php similarity index 85% rename from code/admin/GridFieldFormAction.php rename to src/Admin/GridFieldFormAction.php index 47d448f..dfcc629 100644 --- a/code/admin/GridFieldFormAction.php +++ b/src/Admin/GridFieldFormAction.php @@ -1,5 +1,9 @@ {$this->childMethod}()->Count() > 0) { - $dropdown = new DropdownField('Target', 'Target', $this->records->exclude('ID', $record->ID)->map()); + $dropdown = DropdownField::create('Target', 'Target', $this->records->exclude('ID', $record->ID)->map()); $dropdown->setAttribute('id', 'Target_'.$record->ID); $prefix = strtolower($this->parentMethod . '-' . $this->childMethod); - $action = GridFieldFormAction::create( + $action = new GridFieldFormAction( $gridField, 'MergeAction' . $record->ID, 'Move', diff --git a/code/forms/BlogAdminSidebar.php b/src/Forms/BlogAdminSidebar.php similarity index 73% rename from code/forms/BlogAdminSidebar.php rename to src/Forms/BlogAdminSidebar.php index 67deac5..59c433a 100644 --- a/code/forms/BlogAdminSidebar.php +++ b/src/Forms/BlogAdminSidebar.php @@ -1,5 +1,10 @@ canCreate()) { $id = $gridField->getList()->add($obj); if (!$id) { - $gridField->setError( + $gridField->setCustomValidationMessage( _t( 'GridFieldAddByDBField.AddFail', 'Unable to save {class} to the database.', @@ -87,8 +102,7 @@ class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLP array( 'class' => get_class($obj), ) - ), - 'error' + ) ); } } else { @@ -159,7 +173,7 @@ class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLP $obj = singleton($dataClass); if (!$obj->canCreate()) { - return ""; + return ''; } $dbField = $this->getDataObjectField(); @@ -177,16 +191,17 @@ class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLP $addAction = new GridField_FormAction( $gridField, 'add', - _t('GridFieldAddByDBField.Add', - 'Add {name}', "Add button text", - array( - 'name' => $obj->i18n_singular_name(), - ) + _t( + 'GridFieldAddByDBField.Add', + 'Add {name}', + 'Add button text', + ['name' => $obj->i18n_singular_name()] ), 'add', 'add' ); $addAction->setAttribute('data-icon', 'add'); + $addAction->addExtraClass('btn btn-primary'); $forTemplate = new ArrayData(array()); @@ -194,8 +209,6 @@ class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLP $forTemplate->Fields->push($textField); $forTemplate->Fields->push($addAction); - return array( - $this->targetFragment => $forTemplate->renderWith('GridFieldAddByDBField') - ); + return array($this->targetFragment => $forTemplate->renderWith(self::class)); } } diff --git a/code/forms/gridfield/GridFieldBlogPostState.php b/src/Forms/GridField/GridFieldBlogPostState.php similarity index 94% rename from code/forms/gridfield/GridFieldBlogPostState.php rename to src/Forms/GridField/GridFieldBlogPostState.php index 3d01ce4..609f014 100644 --- a/code/forms/gridfield/GridFieldBlogPostState.php +++ b/src/Forms/GridField/GridFieldBlogPostState.php @@ -1,5 +1,12 @@ removeComponentsByType('GridFieldSiteTreeState'); + $this->removeComponentsByType(GridFieldSiteTreeState::class); $this->addComponent(new GridFieldBlogPostState()); } } diff --git a/code/model/Blog.php b/src/Model/Blog.php similarity index 53% rename from code/model/Blog.php rename to src/Model/Blog.php index 0ce1b08..221787f 100644 --- a/code/model/Blog.php +++ b/src/Model/Blog.php @@ -1,5 +1,34 @@ 'BlogTag', - 'Categories' => 'BlogCategory', + 'Tags' => BlogTag::class, + 'Categories' => BlogCategory::class, ); /** * @var array */ private static $many_many = array( - 'Editors' => 'Member', - 'Writers' => 'Member', - 'Contributors' => 'Member', + 'Editors' => Member::class, + 'Writers' => Member::class, + 'Contributors' => Member::class, ); /** * @var array */ private static $allowed_children = array( - 'BlogPost', + BlogPost::class, ); /** * @var array */ private static $extensions = array( - 'BlogFilter', + BlogFilter::class, ); /** @@ -93,7 +128,7 @@ class Blog extends Page implements PermissionProvider */ private static $defaults = array( 'ProvideComments' => false, - 'PostsPerPage' => 10, + 'PostsPerPage' => 10 ); /** @@ -111,34 +146,47 @@ class Blog extends Page implements PermissionProvider Requirements::css(BLOGGER_DIR . '/css/cms.css'); Requirements::javascript(BLOGGER_DIR . '/js/cms.js'); - $self =& $this; - - $this->beforeUpdateCMSFields(function ($fields) use ($self) { - if (!$self->canEdit()) { + $this->beforeUpdateCMSFields(function ($fields) { + if (!$this->canEdit()) { return; } $categories = GridField::create( 'Categories', _t('Blog.Categories', 'Categories'), - $self->Categories(), - GridFieldCategorisationConfig::create(15, $self->Categories()->sort('Title'), 'BlogCategory', 'Categories', 'BlogPosts') + $this->Categories(), + GridFieldCategorisationConfig::create( + 15, + $this->Categories()->sort('Title'), + BlogCategory::class, + 'Categories', + 'BlogPosts' + ) ); $tags = GridField::create( 'Tags', _t('Blog.Tags', 'Tags'), - $self->Tags(), - GridFieldCategorisationConfig::create(15, $self->Tags()->sort('Title'), 'BlogTag', 'Tags', 'BlogPosts') + $this->Tags(), + GridFieldCategorisationConfig::create( + 15, + $this->Tags()->sort('Title'), + BlogTag::class, + 'Tags', + 'BlogPosts' + ) ); /** * @var FieldList $fields */ - $fields->addFieldsToTab('Root.Categorisation', array( - $categories, - $tags - )); + $fields->addFieldsToTab( + 'Root.Categorisation', + array( + $categories, + $tags + ) + ); $fields->findOrMakeTab('Root.Categorisation')->addExtraClass('blog-cms-categorisation'); }); @@ -228,7 +276,7 @@ class Blog extends Page implements PermissionProvider public function RoleOf($member) { if (is_numeric($member)) { - $member = DataObject::get_by_id('Member', $member); + $member = Member::get()->byId($member); } if (!$member) { @@ -301,14 +349,15 @@ class Blog extends Page implements PermissionProvider { $fields = parent::getSettingsFields(); - $fields->addFieldToTab('Root.Settings', + $fields->addFieldToTab( + 'Root.Settings', NumericField::create('PostsPerPage', _t('Blog.PostsPerPage', 'Posts Per Page')) ); $members = $this->getCandidateUsers()->map()->toArray(); $editorField = ListboxField::create('Editors', 'Editors', $members) - ->setMultiple(true) + // ->setMultiple(true) ->setRightTitle('help') ->setDescription(' An editor has control over specific Blogs, and all posts included within it. Short of being able to assign other editors to a blog, they are able to handle most changes to their assigned blog.
@@ -327,7 +376,7 @@ class Blog extends Page implements PermissionProvider } $writerField = ListboxField::create('Writers', 'Writers', $members) - ->setMultiple(true) + // ->setMultiple(true) ->setRightTitle('help') ->setDescription(' A writer has full control over creating, editing and publishing BlogPosts they have authored or have been assigned to. Writers are unable to edit BlogPosts to which they are not assigned.
@@ -343,7 +392,7 @@ class Blog extends Page implements PermissionProvider } $contributorField = ListboxField::create('Contributors', 'Contributors', $members) - ->setMultiple(true) + // ->setMultiple(true) ->setRightTitle('help') ->setDescription(' Contributors have the ability to create or edit BlogPosts, but are unable to publish without authorisation of an editor. They are also unable to assign other contributing authors to any of their BlogPosts.
@@ -357,11 +406,14 @@ class Blog extends Page implements PermissionProvider $contributorField = $contributorField->performDisabledTransformation(); } - $fields->addFieldsToTab('Root.Users', array( - $editorField, - $writerField, - $contributorField - )); + $fields->addFieldsToTab( + 'Root.Users', + array( + $editorField, + $writerField, + $contributorField + ) + ); return $fields; } @@ -471,7 +523,10 @@ class Blog extends Page implements PermissionProvider $stage = '_' . $stage; } - $query->innerJoin('BlogPost', sprintf('"SiteTree%s"."ID" = "BlogPost%s"."ID"', $stage, $stage)); + $query->innerJoin( + DataObject::getSchema()->tableName(BlogPost::class), + sprintf('"SiteTree%s"."ID" = "BlogPost%s"."ID"', $stage, $stage) + ); $conn = DB::getConn(); @@ -517,7 +572,13 @@ class Blog extends Page implements PermissionProvider */ public function ProfileLink($urlSegment) { - return Controller::join_links($this->Link(), 'profile', $urlSegment); + $baseLink = $this->Link(); + if ($baseLink === '/') { + // Handle homepage blogs + $baseLink = RootURLController::get_homepage_link(); + } + + return Controller::join_links($baseLink, 'profile', $urlSegment); } /** @@ -608,13 +669,13 @@ class Blog extends Page implements PermissionProvider return $group; } - $group = new Group(); + $group = Group::create(); $group->Title = 'Blog users'; $group->Code = $code; $group->write(); - $permission = new Permission(); + $permission = Permission::create(); $permission->Code = $this->config()->grant_user_permission; $group->Permissions()->add($permission); @@ -622,510 +683,3 @@ class Blog extends Page implements PermissionProvider return $group; } } - -/** - * @package silverstripe - * @subpackage blog - */ -class Blog_Controller extends Page_Controller -{ - /** - * @var array - */ - private static $allowed_actions = array( - 'archive', - 'tag', - 'category', - 'rss', - 'profile', - ); - - /** - * @var array - */ - private static $url_handlers = array( - 'tag/$Tag!/$Rss' => 'tag', - 'category/$Category!/$Rss' => 'category', - 'archive/$Year!/$Month/$Day' => 'archive', - 'profile/$URLSegment!' => 'profile', - ); - - /** - * @var array - */ - private static $casting = array( - 'MetaTitle' => 'Text', - 'FilterDescription' => 'Text', - ); - - /** - * The current Blog Post DataList query. - * - * @var DataList - */ - protected $blogPosts; - - /** - * @return string - */ - public function index() - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - - $this->blogPosts = $dataRecord->getBlogPosts(); - - return $this->render(); - } - - /** - * Renders a Blog Member's profile. - * - * @return SS_HTTPResponse - */ - public function profile() - { - $profile = $this->getCurrentProfile(); - - if (!$profile) { - return $this->httpError(404, 'Not Found'); - } - - $this->blogPosts = $this->getCurrentProfilePosts(); - - return $this->render(); - } - - /** - * Get the Member associated with the current URL segment. - * - * @return null|Member - */ - public function getCurrentProfile() - { - $urlSegment = $this->request->param('URLSegment'); - - if ($urlSegment) { - return Member::get() - ->filter('URLSegment', $urlSegment) - ->first(); - } - - return null; - } - - /** - * Get posts related to the current Member profile. - * - * @return null|DataList - */ - public function getCurrentProfilePosts() - { - $profile = $this->getCurrentProfile(); - - if ($profile) { - return $profile->BlogPosts()->filter('ParentID', $this->ID); - } - - return null; - } - - /** - * Renders an archive for a specified date. This can be by year or year/month. - * - * @return null|SS_HTTPResponse - */ - public function archive() - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - - $year = $this->getArchiveYear(); - $month = $this->getArchiveMonth(); - $day = $this->getArchiveDay(); - - if ($this->request->param('Month') && !$month) { - $this->httpError(404, 'Not Found'); - } - - if ($month && $this->request->param('Day') && !$day) { - $this->httpError(404, 'Not Found'); - } - - if ($year) { - $this->blogPosts = $dataRecord->getArchivedBlogPosts($year, $month, $day); - - return $this->render(); - } - - $this->httpError(404, 'Not Found'); - - return null; - } - - /** - * Fetches the archive year from the url. - * - * @return int - */ - public function getArchiveYear() - { - if ($this->request->param('Year')) { - if (preg_match('/^[0-9]{4}$/', $year = $this->request->param('Year'))) { - return (int) $year; - } - } elseif ($this->request->param('Action') == 'archive') { - return SS_Datetime::now()->Year(); - } - - return null; - } - - /** - * Fetches the archive money from the url. - * - * @return null|int - */ - public function getArchiveMonth() - { - $month = $this->request->param('Month'); - - if (preg_match('/^[0-9]{1,2}$/', $month)) { - if ($month > 0 && $month < 13) { - if (checkdate($month, 01, $this->getArchiveYear())) { - return (int) $month; - } - } - } - - return null; - } - - /** - * Fetches the archive day from the url. - * - * @return null|int - */ - public function getArchiveDay() - { - $day = $this->request->param('Day'); - - if (preg_match('/^[0-9]{1,2}$/', $day)) { - if (checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) { - return (int) $day; - } - } - - return null; - } - - /** - * Renders the blog posts for a given tag. - * - * @return null|SS_HTTPResponse - */ - public function tag() - { - $tag = $this->getCurrentTag(); - - if ($tag) { - $this->blogPosts = $tag->BlogPosts(); - - if($this->isRSS()) { - return $this->rssFeed($this->blogPosts, $tag->getLink()); - } else { - return $this->render(); - } - } - - $this->httpError(404, 'Not Found'); - - return null; - } - - /** - * Tag Getter for use in templates. - * - * @return null|BlogTag - */ - public function getCurrentTag() - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - $tag = $this->request->param('Tag'); - if ($tag) { - return $dataRecord->Tags() - ->filter('URLSegment', array($tag, rawurlencode($tag))) - ->first(); - } - return null; - } - - /** - * Renders the blog posts for a given category. - * - * @return null|SS_HTTPResponse - */ - public function category() - { - $category = $this->getCurrentCategory(); - - if ($category) { - $this->blogPosts = $category->BlogPosts(); - - if($this->isRSS()) { - return $this->rssFeed($this->blogPosts, $category->getLink()); - } else { - return $this->render(); - } - } - - $this->httpError(404, 'Not Found'); - - return null; - } - - /** - * Category Getter for use in templates. - * - * @return null|BlogCategory - */ - public function getCurrentCategory() - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - $category = $this->request->param('Category'); - if ($category) { - return $dataRecord->Categories() - ->filter('URLSegment', array($category, rawurlencode($category))) - ->first(); - } - return null; - } - - /** - * Get the meta title for the current action. - * - * @return string - */ - public function getMetaTitle() - { - $title = $this->data()->getTitle(); - $filter = $this->getFilterDescription(); - - if ($filter) { - $title = sprintf('%s - %s', $title, $filter); - } - - $this->extend('updateMetaTitle', $title); - - return $title; - } - - /** - * Returns a description of the current filter. - * - * @return string - */ - public function getFilterDescription() - { - $items = array(); - - $list = $this->PaginatedList(); - $currentPage = $list->CurrentPage(); - - if ($currentPage > 1) { - $items[] = _t( - 'Blog.FILTERDESCRIPTION_PAGE', - 'Page {page}', - null, - array( - 'page' => $currentPage, - ) - ); - } - - if ($author = $this->getCurrentProfile()) { - $items[] = _t( - 'Blog.FILTERDESCRIPTION_AUTHOR', - 'By {author}', - null, - array( - 'author' => $author->Title, - ) - ); - } - - if ($tag = $this->getCurrentTag()) { - $items[] = _t( - 'Blog.FILTERDESCRIPTION_TAG', - 'Tagged with {tag}', - null, - array( - 'tag' => $tag->Title, - ) - ); - } - - if ($category = $this->getCurrentCategory()) { - $items[] = _t( - 'Blog.FILTERDESCRIPTION_CATEGORY', - 'In category {category}', - null, - array( - 'category' => $category->Title, - ) - ); - } - - if ($this->owner->getArchiveYear()) { - if ($this->owner->getArchiveDay()) { - $date = $this->owner->getArchiveDate()->Nice(); - } elseif ($this->owner->getArchiveMonth()) { - $date = $this->owner->getArchiveDate()->format('F, Y'); - } else { - $date = $this->owner->getArchiveDate()->format('Y'); - } - - $items[] = _t( - 'Blog.FILTERDESCRIPTION_DATE', - 'In {date}', - null, - array( - 'date' => $date, - ) - ); - } - - $result = ''; - - if ($items) { - $result = implode(', ', $items); - } - - $this->extend('updateFilterDescription', $result); - - return $result; - } - - /** - * Returns a list of paginated blog posts based on the BlogPost dataList. - * - * @return PaginatedList - */ - public function PaginatedList() - { - $allPosts = $this->blogPosts ?: new ArrayList(); - - $posts = new PaginatedList($allPosts); - - // Set appropriate page size - if ($this->PostsPerPage > 0) { - $pageSize = $this->PostsPerPage; - } elseif ($count = $allPosts->count()) { - $pageSize = $count; - } else { - $pageSize = 99999; - } - $posts->setPageLength($pageSize); - - // Set current page - $start = $this->request->getVar($posts->getPaginationGetVar()); - $posts->setPageStart($start); - - return $posts; - } - - /** - * Displays an RSS feed of blog posts. - * - * @return string - */ - public function rss() - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - - $this->blogPosts = $dataRecord->getBlogPosts(); - - return $this->rssFeed($this->blogPosts, $this->Link()); - } - - /** - * Returns the current archive date. - * - * @return null|Date - */ - public function getArchiveDate() - { - $year = $this->getArchiveYear(); - $month = $this->getArchiveMonth(); - $day = $this->getArchiveDay(); - - if ($year) { - if ($month) { - $date = sprintf('%s-%s-01', $year, $month); - - if ($day) { - $date = sprintf('%s-%s-%s', $year, $month, $day); - } - } else { - $date = sprintf('%s-01-01', $year); - } - - return DBField::create_field('Date', $date); - } - - return null; - } - - /** - * Returns a link to the RSS feed. - * - * @return string - */ - public function getRSSLink() - { - return $this->Link('rss'); - } - - /** - * Displays an RSS feed of the given blog posts. - * - * @param DataList $blogPosts - * @param string $link - * - * @return string - */ - protected function rssFeed($blogPosts, $link) - { - $rss = new RSSFeed($blogPosts, $link, $this->MetaTitle, $this->MetaDescription); - - $this->extend('updateRss', $rss); - - return $rss->outputToBrowser(); - } - - /** - * Returns true if the $Rss sub-action for categories/tags has been set to "rss" - */ - private function isRSS() - { - $rss = $this->request->param('Rss'); - if(is_string($rss) && strcasecmp($rss, "rss") == 0) { - return true; - } else { - return false; - } - } -} diff --git a/src/Model/BlogCategory.php b/src/Model/BlogCategory.php new file mode 100644 index 0000000..e527d71 --- /dev/null +++ b/src/Model/BlogCategory.php @@ -0,0 +1,79 @@ + 'Varchar(255)', + 'URLSegment' => 'Varchar(255)' + ); + + /** + * @var array + */ + private static $has_one = array( + 'Blog' => Blog::class, + ); + + /** + * @var array + */ + private static $belongs_many_many = array( + 'BlogPosts' => BlogPost::class, + ); + + /** + * {@inheritdoc} + */ + protected function getListUrlSegment() + { + return 'category'; + } + + /** + * {@inheritdoc} + */ + protected function getDuplicateError() + { + return _t('BlogCategory.Duplicate', 'A blog category already exists with that name.'); + } +} diff --git a/code/extensions/BlogCommentExtension.php b/src/Model/BlogCommentExtension.php similarity index 82% rename from code/extensions/BlogCommentExtension.php rename to src/Model/BlogCommentExtension.php index de4576a..9768a60 100644 --- a/code/extensions/BlogCommentExtension.php +++ b/src/Model/BlogCommentExtension.php @@ -1,5 +1,10 @@ 'tag', + 'category/$Category!/$Rss' => 'category', + 'archive/$Year!/$Month/$Day' => 'archive', + 'profile/$URLSegment!' => 'profile' + ); + + /** + * @var array + */ + private static $casting = array( + 'MetaTitle' => 'Text', + 'FilterDescription' => 'Text' + ); + + /** + * The current Blog Post DataList query. + * + * @var DataList + */ + protected $blogPosts; + + /** + * @return string + */ + public function index() + { + /** + * @var Blog $dataRecord + */ + $dataRecord = $this->dataRecord; + + $this->blogPosts = $dataRecord->getBlogPosts(); + + return $this->render(); + } + + /** + * Renders a Blog Member's profile. + * + * @return HTTPResponse + */ + public function profile() + { + $profile = $this->getCurrentProfile(); + + if (!$profile) { + return $this->httpError(404, 'Not Found'); + } + + $this->blogPosts = $this->getCurrentProfilePosts(); + + return $this->render(); + } + + /** + * Get the Member associated with the current URL segment. + * + * @return null|Member + */ + public function getCurrentProfile() + { + $urlSegment = $this->request->param('URLSegment'); + + if ($urlSegment) { + return Member::get() + ->filter('URLSegment', $urlSegment) + ->first(); + } + + return null; + } + + /** + * Get posts related to the current Member profile. + * + * @return null|DataList + */ + public function getCurrentProfilePosts() + { + $profile = $this->getCurrentProfile(); + + if ($profile) { + return $profile->BlogPosts()->filter('ParentID', $this->ID); + } + + return null; + } + + /** + * Renders an archive for a specified date. This can be by year or year/month. + * + * @return null|HTTPResponse + */ + public function archive() + { + /** + * @var Blog $dataRecord + */ + $dataRecord = $this->dataRecord; + + $year = $this->getArchiveYear(); + $month = $this->getArchiveMonth(); + $day = $this->getArchiveDay(); + + if ($this->request->param('Month') && !$month) { + $this->httpError(404, 'Not Found'); + } + + if ($month && $this->request->param('Day') && !$day) { + $this->httpError(404, 'Not Found'); + } + + if ($year) { + $this->blogPosts = $dataRecord->getArchivedBlogPosts($year, $month, $day); + + return $this->render(); + } + + $this->httpError(404, 'Not Found'); + + return null; + } + + /** + * Fetches the archive year from the url. + * + * @return int + */ + public function getArchiveYear() + { + if ($this->request->param('Year')) { + if (preg_match('/^[0-9]{4}$/', $year = $this->request->param('Year'))) { + return (int) $year; + } + } elseif ($this->request->param('Action') == 'archive') { + return DBDatetime::now()->Year(); + } + + return null; + } + + /** + * Fetches the archive money from the url. + * + * @return null|int + */ + public function getArchiveMonth() + { + $month = $this->request->param('Month'); + + if (preg_match('/^[0-9]{1,2}$/', $month)) { + if ($month > 0 && $month < 13) { + if (checkdate($month, 01, $this->getArchiveYear())) { + return (int) $month; + } + } + } + + return null; + } + + /** + * Fetches the archive day from the url. + * + * @return null|int + */ + public function getArchiveDay() + { + $day = $this->request->param('Day'); + + if (preg_match('/^[0-9]{1,2}$/', $day)) { + if (checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) { + return (int) $day; + } + } + + return null; + } + + /** + * Renders the blog posts for a given tag. + * + * @return null|HTTPResponse + */ + public function tag() + { + $tag = $this->getCurrentTag(); + + if ($tag) { + $this->blogPosts = $tag->BlogPosts(); + + if($this->isRSS()) { + return $this->rssFeed($this->blogPosts, $tag->getLink()); + } else { + return $this->render(); + } + } + + $this->httpError(404, 'Not Found'); + + return null; + } + + /** + * Tag Getter for use in templates. + * + * @return null|BlogTag + */ + public function getCurrentTag() + { + /** + * @var Blog $dataRecord + */ + $dataRecord = $this->dataRecord; + $tag = $this->request->param('Tag'); + if ($tag) { + return $dataRecord->Tags() + ->filter('URLSegment', array($tag, rawurlencode($tag))) + ->first(); + } + return null; + } + + /** + * Renders the blog posts for a given category. + * + * @return null|HTTPResponse + */ + public function category() + { + $category = $this->getCurrentCategory(); + + if ($category) { + $this->blogPosts = $category->BlogPosts(); + + if($this->isRSS()) { + return $this->rssFeed($this->blogPosts, $category->getLink()); + } else { + return $this->render(); + } + } + + $this->httpError(404, 'Not Found'); + + return null; + } + + /** + * Category Getter for use in templates. + * + * @return null|BlogCategory + */ + public function getCurrentCategory() + { + /** + * @var Blog $dataRecord + */ + $dataRecord = $this->dataRecord; + $category = $this->request->param('Category'); + if ($category) { + return $dataRecord->Categories() + ->filter('URLSegment', array($category, rawurlencode($category))) + ->first(); + } + return null; + } + + /** + * Get the meta title for the current action. + * + * @return string + */ + public function getMetaTitle() + { + $title = $this->data()->getTitle(); + $filter = $this->getFilterDescription(); + + if ($filter) { + $title = sprintf('%s - %s', $title, $filter); + } + + $this->extend('updateMetaTitle', $title); + + return $title; + } + + /** + * Returns a description of the current filter. + * + * @return string + */ + public function getFilterDescription() + { + $items = array(); + + $list = $this->PaginatedList(); + $currentPage = $list->CurrentPage(); + + if ($currentPage > 1) { + $items[] = _t( + 'Blog.FILTERDESCRIPTION_PAGE', + 'Page {page}', + null, + array( + 'page' => $currentPage + ) + ); + } + + if ($author = $this->getCurrentProfile()) { + $items[] = _t( + 'Blog.FILTERDESCRIPTION_AUTHOR', + 'By {author}', + null, + array( + 'author' => $author->Title + ) + ); + } + + if ($tag = $this->getCurrentTag()) { + $items[] = _t( + 'Blog.FILTERDESCRIPTION_TAG', + 'Tagged with {tag}', + null, + array( + 'tag' => $tag->Title + ) + ); + } + + if ($category = $this->getCurrentCategory()) { + $items[] = _t( + 'Blog.FILTERDESCRIPTION_CATEGORY', + 'In category {category}', + null, + array( + 'category' => $category->Title + ) + ); + } + + if ($this->owner->getArchiveYear()) { + if ($this->owner->getArchiveDay()) { + $date = $this->owner->getArchiveDate()->Nice(); + } elseif ($this->owner->getArchiveMonth()) { + $date = $this->owner->getArchiveDate()->format('F, Y'); + } else { + $date = $this->owner->getArchiveDate()->format('Y'); + } + + $items[] = _t( + 'Blog.FILTERDESCRIPTION_DATE', + 'In {date}', + null, + array( + 'date' => $date, + ) + ); + } + + $result = ''; + + if ($items) { + $result = implode(', ', $items); + } + + $this->extend('updateFilterDescription', $result); + + return $result; + } + + /** + * Returns a list of paginated blog posts based on the BlogPost dataList. + * + * @return PaginatedList + */ + public function PaginatedList() + { + $allPosts = $this->blogPosts ?: ArrayList::create(); + $posts = PaginatedList::create($allPosts); + + // Set appropriate page size + if ($this->PostsPerPage > 0) { + $pageSize = $this->PostsPerPage; + } elseif ($count = $allPosts->count()) { + $pageSize = $count; + } else { + $pageSize = 99999; + } + $posts->setPageLength($pageSize); + + // Set current page + $start = $this->request->getVar($posts->getPaginationGetVar()); + $posts->setPageStart($start); + + return $posts; + } + + /** + * Displays an RSS feed of blog posts. + * + * @return string + */ + public function rss() + { + /** + * @var Blog $dataRecord + */ + $dataRecord = $this->dataRecord; + + $this->blogPosts = $dataRecord->getBlogPosts(); + + return $this->rssFeed($this->blogPosts, $this->Link()); + } + + /** + * Returns the current archive date. + * + * @return null|Date + */ + public function getArchiveDate() + { + $year = $this->getArchiveYear(); + $month = $this->getArchiveMonth(); + $day = $this->getArchiveDay(); + + if ($year) { + if ($month) { + $date = sprintf('%s-%s-01', $year, $month); + + if ($day) { + $date = sprintf('%s-%s-%s', $year, $month, $day); + } + } else { + $date = sprintf('%s-01-01', $year); + } + + $obj = DBDatetime::create('date'); + $obj->setValue($date); + return $obj; + } + + return null; + } + + /** + * Returns a link to the RSS feed. + * + * @return string + */ + public function getRSSLink() + { + return $this->Link('rss'); + } + + /** + * Displays an RSS feed of the given blog posts. + * + * @param DataList $blogPosts + * @param string $link + * + * @return string + */ + protected function rssFeed($blogPosts, $link) + { + $rss = new RSSFeed($blogPosts, $link, $this->MetaTitle, $this->MetaDescription); + + $this->extend('updateRss', $rss); + + return $rss->outputToBrowser(); + } + + /** + * Returns true if the $Rss sub-action for categories/tags has been set to "rss" + * + * @return bool + */ + protected function isRSS() + { + $rss = $this->request->param('Rss'); + return (is_string($rss) && strcasecmp($rss, 'rss') == 0); + } +} diff --git a/code/extensions/BlogFilter.php b/src/Model/BlogFilter.php similarity index 63% rename from code/extensions/BlogFilter.php rename to src/Model/BlogFilter.php index 65dc027..da53fc3 100644 --- a/code/extensions/BlogFilter.php +++ b/src/Model/BlogFilter.php @@ -1,5 +1,20 @@ shouldFilter() && $this->subclassForBlog() && !Permission::check('VIEW_DRAFT_CONTENT')) { - $stage = Versioned::current_stage(); + $stage = Versioned::get_stage(); if ($stage == 'Stage') { $stage = ''; @@ -26,8 +41,11 @@ class BlogFilter extends Lumberjack } $dataQuery = $staged->dataQuery() - ->innerJoin('BlogPost', sprintf('"BlogPost%s"."ID" = "SiteTree%s"."ID"', $stage, $stage)) - ->where(sprintf('"PublishDate" < \'%s\'', Convert::raw2sql(SS_Datetime::now()))); + ->innerJoin( + DataObject::getSchema()->tableName(BlogPost::class), + sprintf('"BlogPost%s"."ID" = "SiteTree%s"."ID"', $stage, $stage) + ) + ->where(sprintf('"PublishDate" < \'%s\'', Convert::raw2sql(DBDatetime::now()))); $staged = $staged->setDataQuery($dataQuery); } @@ -40,7 +58,7 @@ class BlogFilter extends Lumberjack */ protected function subclassForBlog() { - return in_array(get_class($this->owner), ClassInfo::subClassesFor('Blog')); + return in_array(get_class($this->owner), ClassInfo::subclassesFor(Blog::class)); } /** @@ -52,8 +70,11 @@ class BlogFilter extends Lumberjack if (!$this->shouldFilter() && $this->isBlog() && !Permission::check('VIEW_DRAFT_CONTENT')) { $dataQuery = $staged->dataQuery() - ->innerJoin('BlogPost', '"BlogPost_Live"."ID" = "SiteTree_Live"."ID"') - ->where(sprintf('"PublishDate" < \'%s\'', Convert::raw2sql(SS_Datetime::now()))); + ->innerJoin( + DataObject::getSchema()->tableName(BlogPost::class), + '"BlogPost_Live"."ID" = "SiteTree_Live"."ID"' + ) + ->where(sprintf('"PublishDate" < \'%s\'', Convert::raw2sql(DBDatetime::now()))); $staged = $staged->setDataQuery($dataQuery); } @@ -82,33 +103,16 @@ class BlogFilter extends Lumberjack 'ClassName' => $excluded )); - $gridField = new BlogFilter_GridField( + $gridField = BlogFilterGridField::create( 'ChildPages', $this->getLumberjackTitle(), $pages, $this->getLumberjackGridFieldConfig() ); - $tab = new Tab('ChildPages', $this->getLumberjackTitle(), $gridField); + $tab = Tab::create('ChildPages', $this->getLumberjackTitle(), $gridField); $fields->insertBefore($tab, 'Main'); } } } - - -/** - * Enables children of non-editable pages to be edited. - */ -class BlogFilter_GridField extends GridField -{ - /** - * @param FormTransformation $transformation - * - * @return $this - */ - public function transform(FormTransformation $transformation) - { - return $this; - } -} diff --git a/src/Model/BlogFilter/BlogFilterGridField.php b/src/Model/BlogFilter/BlogFilterGridField.php new file mode 100644 index 0000000..190ee1d --- /dev/null +++ b/src/Model/BlogFilter/BlogFilterGridField.php @@ -0,0 +1,22 @@ + 'Varchar', - 'BlogProfileSummary' => 'Text', + 'URLSegment' => 'Varchar', + 'BlogProfileSummary' => 'Text' ); /** * @var array */ private static $has_one = array( - 'BlogProfileImage' => 'Image', + 'BlogProfileImage' => Image::class ); /** * @var array */ private static $belongs_many_many = array( - 'BlogPosts' => 'BlogPost', + 'BlogPosts' => BlogPost::class ); /** @@ -80,7 +93,6 @@ class BlogMemberExtension extends DataExtension return $conflict->count() == 0; } - /** * {@inheritdoc} */ @@ -97,13 +109,13 @@ class BlogMemberExtension extends DataExtension Requirements::css(BLOGGER_DIR . '/css/cms.css'); Requirements::javascript(BLOGGER_DIR . '/js/cms.js'); - $tab = new Tab('BlogPosts', 'Blog Posts'); + $tab = Tab::create('BlogPosts', 'Blog Posts'); - $gridField = new GridField( + $gridField = GridField::create( 'BlogPosts', 'Blog Posts', $this->owner->BlogPosts(), - new GridFieldConfig_BlogPost() + GridFieldConfig_BlogPost::create() ); $tab->Fields()->add($gridField); diff --git a/src/Model/BlogObject.php b/src/Model/BlogObject.php new file mode 100644 index 0000000..fd8bded --- /dev/null +++ b/src/Model/BlogObject.php @@ -0,0 +1,246 @@ +extend('updateGetBlogPosts', $blogPosts); + + return $blogPosts; + } + + /** + * {@inheritdoc} + */ + public function getCMSFields() + { + $fields = TabSet::create( + 'Root', + Tab::create( + 'Main', + TextField::create('Title', _t(self::class . '.Title', 'Title')) + ) + ); + + $fields = FieldList::create($fields); + $this->extend('updateCMSFields', $fields); + + return $fields; + } + + /** + * {@inheritdoc} + * @return ValidationResult + */ + public function validate() + { + /** @var ValidationResult $validation */ + $validation = parent::validate(); + if (!$validation->isValid()) { + return $validation; + } + + $blog = $this->Blog(); + if (!$blog || !$blog->exists()) { + return $validation; + } + + if ($this->getDuplicatesByField('Title')->count() > 0) { + $validation->addError($this->getDuplicateError(), self::DUPLICATE_EXCEPTION); + } + + return $validation; + } + + /** + * Returns a relative link to this category. + * + * @return string + */ + public function getLink() + { + return Controller::join_links( + $this->Blog()->Link(), + $this->getListUrlSegment(), + $this->URLSegment + ); + } + + /** + * Inherits from the parent blog or can be overwritten using a DataExtension. + * + * @param null|Member $member + * + * @return bool + */ + public function canView($member = null) + { + $extended = $this->extendedCan(__FUNCTION__, $member); + + if ($extended !== null) { + return $extended; + } + + return $this->Blog()->canView($member); + } + + /** + * {@inheritdoc} + */ + public function canCreate($member = null, $context = array()) + { + $extended = $this->extendedCan(__FUNCTION__, $member); + + if ($extended !== null) { + return $extended; + } + + $permission = Blog::config()->grant_user_permission; + + return Permission::checkMember($member, $permission); + } + + /** + * Inherits from the parent blog or can be overwritten using a DataExtension. + * + * @param null|Member $member + * + * @return bool + */ + public function canDelete($member = null) + { + $extended = $this->extendedCan(__FUNCTION__, $member); + + if ($extended !== null) { + return $extended; + } + + return $this->Blog()->canDelete($member); + } + + /** + * Inherits from the parent blog or can be overwritten using a DataExtension. + * + * @param null|Member $member + * + * @return bool + */ + public function canEdit($member = null) + { + $extended = $this->extendedCan(__FUNCTION__, $member); + + if ($extended !== null) { + return $extended; + } + + return $this->Blog()->canEdit($member); + } + + /** + * {@inheritdoc} + */ + protected function onBeforeWrite() + { + parent::onBeforeWrite(); + if ($this->exists() || empty($this->URLSegment)) { + return $this->generateURLSegment(); + } + } + + /** + * Generates a unique URLSegment from the title. + * + * @param int $increment + * + * @return string + */ + public function generateURLSegment($increment = 0) + { + $increment = (int) $increment; + $filter = URLSegmentFilter::create(); + + // Setting this to on. Because of the UI flow, it would be quite a lot of work + // to support turning this off. (ie. the add by title flow would not work). + // If this becomes a problem we can approach it then. + // @see https://github.com/silverstripe/silverstripe-blog/issues/376 + $filter->setAllowMultibyte(true); + + $this->URLSegment = $filter->filter($this->Title); + + if ($increment > 0) { + $this->URLSegment .= '-' . $increment; + } + + if ($this->getDuplicatesByField('URLSegment')->count() > 0) { + $this->generateURLSegment($increment + 1); + } + + return $this->URLSegment; + } + + /** + * Looks for objects o the same type and the same value by the given Field + * + * @param string $field E.g. URLSegment or Title + * @return DataList + */ + protected function getDuplicatesByField($field) + { + $duplicates = DataList::create(self::class) + ->filter( + [ + $field => $this->$field, + 'BlogID' => (int) $this->BlogID + ] + ); + + if ($this->ID) { + $duplicates = $duplicates->exclude('ID', $this->ID); + } + + return $duplicates; + } + + /** + * This returns the url segment for the listing page. + * eg. 'categories' in /my-blog/categories/category-url + * + * This is not editable at the moment, but a method is being used incase we want + * to make it editable in the future. We can use this method to provide logic + * without replacing multiple areas of the code base. We're also not being opinionated + * about how the segment should be obtained at the moment and allowing for the + * implementation to decide. + * + * @return string + */ + abstract protected function getListUrlSegment(); + + /** + * Returns an error message for this object when it tries to write a duplicate. + * + * @return string + */ + abstract protected function getDuplicateError(); +} diff --git a/code/model/BlogPost.php b/src/Model/BlogPost.php similarity index 81% rename from code/model/BlogPost.php rename to src/Model/BlogPost.php index 7e6ac15..ace1085 100644 --- a/code/model/BlogPost.php +++ b/src/Model/BlogPost.php @@ -1,5 +1,31 @@ 'SS_Datetime', + 'PublishDate' => 'Datetime', 'AuthorNames' => 'Varchar(1024)', - 'Summary' => 'HTMLText', + 'Summary' => 'HTMLText' ); /** * @var array */ private static $has_one = array( - 'FeaturedImage' => 'Image', + 'FeaturedImage' => Image::class ); /** * @var array */ private static $many_many = array( - 'Categories' => 'BlogCategory', - 'Tags' => 'BlogTag', - 'Authors' => 'Member', + 'Categories' => BlogCategory::class, + 'Tags' => BlogTag::class, + 'Authors' => Member::class ); /** * @var array */ private static $defaults = array( - 'ShowInMenus' => false, - 'InheritSideBar' => true, - 'ProvideComments' => true, + 'ShowInMenus' => false, + 'InheritSideBar' => true, + 'ProvideComments' => true ); /** * @var array */ private static $extensions = array( - 'BlogPostFilter', + BlogPostFilter::class ); /** * @var array */ private static $searchable_fields = array( - 'Title', + 'Title' ); /** * @var array */ private static $summary_fields = array( - 'Title', + 'Title' ); /** @@ -84,7 +116,7 @@ class BlogPost extends Page */ private static $casting = array( 'Excerpt' => 'HTMLText', - 'Date' => 'SS_Datetime', + 'Date' => 'DBDatetime' ); /** @@ -174,9 +206,7 @@ class BlogPost extends Page Requirements::css(BLOGGER_DIR . '/css/cms.css'); Requirements::javascript(BLOGGER_DIR . '/js/cms.js'); - $self =& $this; - - $this->beforeUpdateCMSFields(function ($fields) use ($self) { + $this->beforeUpdateCMSFields(function ($fields) { $uploadField = UploadField::create('FeaturedImage', _t('BlogPost.FeaturedImage', 'Featured Image')); $uploadField->getValidator()->setAllowedExtensions(array('jpg', 'jpeg', 'png', 'gif')); @@ -204,10 +234,8 @@ class BlogPost extends Page $fields->insertAfter($summaryHolder, 'FeaturedImage'); - $fields->push(HiddenField::create('MenuTitle')); - $urlSegment = $fields->dataFieldByName('URLSegment'); - $urlSegment->setURLPrefix($self->Parent()->RelativeLink()); + $urlSegment->setURLPrefix($this->Parent()->RelativeLink()); $fields->removeFieldsFromTab('Root.Main', array( 'MenuTitle', @@ -217,35 +245,39 @@ class BlogPost extends Page $authorField = ListboxField::create( 'Authors', _t('BlogPost.Authors', 'Authors'), - $self->getCandidateAuthors()->map()->toArray() - )->setMultiple(true); + $this->getCandidateAuthors()->map()->toArray() + ); $authorNames = TextField::create( 'AuthorNames', _t('BlogPost.AdditionalCredits', 'Additional Credits'), null, 1024 - )->setDescription(_t( + )->setDescription( + _t( 'BlogPost.AdditionalCredits_Description', - 'If some authors of this post don\'t have CMS access, enter their name(s) here. You can separate multiple names with a comma.') + 'If some authors of this post don\'t have CMS access, enter their name(s) here. You can separate multiple names with a comma.' + ) ); - if (!$self->canEditAuthors()) { + if (!$this->canEditAuthors()) { $authorField = $authorField->performDisabledTransformation(); $authorNames = $authorNames->performDisabledTransformation(); } $publishDate = DatetimeField::create('PublishDate', _t('BlogPost.PublishDate', 'Publish Date')); $publishDate->getDateField()->setConfig('showcalendar', true); - if (!$self->PublishDate) { - $publishDate->setDescription(_t( + if (!$this->PublishDate) { + $publishDate->setDescription( + _t( 'BlogPost.PublishDate_Description', - 'Will be set to "now" if published without a value.') + 'Will be set to "now" if published without a value.' + ) ); } // Get categories and tags - $parent = $self->Parent(); + $parent = $this->Parent(); $categories = $parent instanceof Blog ? $parent->Categories() : BlogCategory::get(); @@ -253,32 +285,36 @@ class BlogPost extends Page ? $parent->Tags() : BlogTag::get(); - $options = BlogAdminSidebar::create( - $publishDate, - $urlSegment, - TagField::create( - 'Categories', - _t('BlogPost.Categories', 'Categories'), - $categories, - $self->Categories() - ) - ->setCanCreate($self->canCreateCategories()) - ->setShouldLazyLoad(true), - TagField::create( - 'Tags', - _t('BlogPost.Tags', 'Tags'), - $tags, - $self->Tags() - ) - ->setCanCreate($self->canCreateTags()) - ->setShouldLazyLoad(true), - $authorField, - $authorNames - )->setTitle('Post Options'); - - $options->setName('blog-admin-sidebar'); - - $fields->insertBefore($options, 'Root'); + // @todo: Reimplement the sidebar + // $options = BlogAdminSidebar::create( + $fields->addFieldsToTab( + 'Root.PostOptions', + [ + $publishDate, + $urlSegment, + TagField::create( + 'Categories', + _t('BlogPost.Categories', 'Categories'), + $categories, + $this->Categories() + ) + ->setCanCreate($this->canCreateCategories()) + ->setShouldLazyLoad(true), + TagField::create( + 'Tags', + _t('BlogPost.Tags', 'Tags'), + $tags, + $this->Tags() + ) + ->setCanCreate($this->canCreateTags()) + ->setShouldLazyLoad(true), + $authorField, + $authorNames + ] + ); + // )->setTitle('Post Options'); + // $options->setName('blog-admin-sidebar'); + // $fields->insertBefore($options, 'Root'); }); $fields = parent::getCMSFields(); @@ -297,11 +333,11 @@ class BlogPost extends Page { if ($this->config()->restrict_authors_to_group) { return Group::get()->filter('Code', $this->config()->restrict_authors_to_group)->first()->Members(); - } else { - $list = Member::get(); - $this->extend('updateCandidateAuthors', $list); - return $list; } + + $list = Member::get(); + $this->extend('updateCandidateAuthors', $list); + return $list; } /** @@ -414,12 +450,12 @@ class BlogPost extends Page public function onBeforePublish() { /** - * @var SS_Datetime $publishDate + * @var DBDatetime $publishDate */ $publishDate = $this->dbObject('PublishDate'); if (!$publishDate->getValue()) { - $this->PublishDate = SS_Datetime::now()->getValue(); + $this->PublishDate = DBDatetime::now()->getValue(); $this->write(); } } @@ -461,15 +497,15 @@ class BlogPost extends Page return false; } - if($this->canEdit($member)) { + if ($this->canEdit($member)) { return true; } /** - * @var SS_Datetime $publishDate + * @var DBDatetime $publishDate */ $publishDate = $this->dbObject('PublishDate'); - if(!$publishDate->exists()) { + if (!$publishDate->exists()) { return false; } @@ -565,7 +601,7 @@ class BlogPost extends Page public function getMonthlyArchiveLink($type = 'day') { /** - * @var SS_Datetime $date + * @var DBDatetime $date */ $date = $this->dbObject('PublishDate'); @@ -593,7 +629,7 @@ class BlogPost extends Page public function getYearlyArchiveLink() { /** - * @var SS_Datetime $date + * @var DBDatetime $date */ $date = $this->dbObject('PublishDate'); @@ -607,7 +643,7 @@ class BlogPost extends Page */ public function getCredits() { - $list = new ArrayList(); + $list = ArrayList::create(); $list->merge($this->getDynamicCredits()); $list->merge($this->getStaticCredits()); @@ -708,11 +744,3 @@ class BlogPost extends Page } } } - -/** - * @package silverstripe - * @subpackage blog - */ -class BlogPost_Controller extends Page_Controller -{ -} diff --git a/src/Model/BlogPostController.php b/src/Model/BlogPostController.php new file mode 100644 index 0000000..2b09699 --- /dev/null +++ b/src/Model/BlogPostController.php @@ -0,0 +1,14 @@ +addWhere(sprintf( + '"PublishDate" < \'%s\'', + Convert::raw2sql(DBDatetime::now()) + )); + } + } + + /** + * {@inheritDoc} + * + * This is a fix so that when we try to fetch subclasses of BlogPost, lazy loading includes the + * BlogPost table in its query. Leaving this table out means the default sort order column + * PublishDate causes an error. + * + * @see https://github.com/silverstripe/silverstripe-framework/issues/1682 + * + * @param SQLSelect $query + * @param DataQuery $dataQuery + * @param DataObject $dataObject + */ + public function augmentLoadLazyFields(SQLSelect &$query, DataQuery &$dataQuery = null, $dataObject) + { + $dataQuery->innerJoin( + DataObject::getSchema()->tableName(BlogPost::class), + '"SiteTree"."ID" = "BlogPost"."ID"' + ); + } +} diff --git a/code/extensions/BlogPostNotifications.php b/src/Model/BlogPostNotifications.php similarity index 91% rename from code/extensions/BlogPostNotifications.php rename to src/Model/BlogPostNotifications.php index e3d7ca2..733c338 100644 --- a/code/extensions/BlogPostNotifications.php +++ b/src/Model/BlogPostNotifications.php @@ -1,5 +1,9 @@ 'Varchar(255)', + 'URLSegment' => 'Varchar(255)' + ); + + /** + * @var array + */ + private static $has_one = array( + 'Blog' => Blog::class + ); + + /** + * @var array + */ + private static $belongs_many_many = array( + 'BlogPosts' => BlogPost::class + ); + + /** + * {@inheritdoc} + */ + protected function getListUrlSegment() + { + return 'tag'; + } + + /** + * {@inheritdoc} + */ + protected function getDuplicateError() + { + return _t('BlogTag.Duplicate', 'A blog tag already exists with that name.'); + } +} diff --git a/code/model/CategorisationObject.php b/src/Model/CategorisationObject.php similarity index 69% rename from code/model/CategorisationObject.php rename to src/Model/CategorisationObject.php index 811419e..827035b 100644 --- a/code/model/CategorisationObject.php +++ b/src/Model/CategorisationObject.php @@ -1,8 +1,11 @@ 'Blog', + 'Blog' => Blog::class, ); /** @@ -54,13 +62,11 @@ class BlogArchiveWidget extends Widget */ public function getCMSFields() { - $self =& $this; - - $this->beforeUpdateCMSFields(function ($fields) use ($self) { + $this->beforeUpdateCMSFields(function ($fields) { /** * @var Enum $archiveType */ - $archiveType = $self->dbObject('ArchiveType'); + $archiveType = $this->dbObject('ArchiveType'); $type = $archiveType->enumValues(); @@ -72,7 +78,11 @@ class BlogArchiveWidget extends Widget * @var FieldList $fields */ $fields->merge(array( - DropdownField::create('BlogID', _t('BlogArchiveWidget.Blog', 'Blog'), Blog::get()->map()), + DropdownField::create( + 'BlogID', + _t('BlogArchiveWidget.Blog', 'Blog'), + Blog::get()->map() + ), DropdownField::create('ArchiveType', _t('BlogArchiveWidget.ArchiveType', 'ArchiveType'), $type), NumericField::create('NumberToDisplay', _t('BlogArchiveWidget.NumberToDisplay', 'No. to Display')) )); @@ -132,7 +142,3 @@ class BlogArchiveWidget extends Widget return $archive; } } - -class BlogArchiveWidget_Controller extends Widget_Controller -{ -} diff --git a/src/Widgets/BlogArchiveWidgetController.php b/src/Widgets/BlogArchiveWidgetController.php new file mode 100644 index 0000000..5e01105 --- /dev/null +++ b/src/Widgets/BlogArchiveWidgetController.php @@ -0,0 +1,14 @@ + 'Int', + 'Order' => 'Varchar', + 'Direction' => 'Varchar', + ); + + /** + * @var array + */ + private static $has_one = array( + 'Blog' => Blog::class, + ); + + /** + * {@inheritdoc} + */ + public function getCMSFields() + { + $this->beforeUpdateCMSFields(function (FieldList $fields) { + $fields[] = DropdownField::create( + 'BlogID', + _t('BlogCategoriesWidget.Blog', 'Blog'), + Blog::get()->map() + ); + + $fields[] = NumericField::create( + 'Limit', + _t('BlogCategoriesWidget.Limit.Label', 'Limit'), + 0 + ) + ->setDescription( + _t( + 'BlogCategoriesWidget.Limit.Description', + 'Limit the number of categories shown by this widget (set to 0 to show all categories).' + ) + ) + ->setMaxLength(3); + + $fields[] = DropdownField::create( + 'Order', + _t('BlogCategoriesWidget.Sort.Label', 'Sort'), + array('Title' => 'Title', 'Created' => 'Created', 'LastEdited' => 'Updated') + ) + ->setDescription( + _t('BlogCategoriesWidget.Sort.Description', 'Change the order of categories shown by this widget.') + ); + + $fields[] = DropdownField::create( + 'Direction', + _t('BlogCategoriesWidget.Direction.Label', 'Direction'), + array('ASC' => 'Ascending', 'DESC' => 'Descending') + ) + ->setDescription( + _t( + 'BlogCategoriesWidget.Direction.Description', + 'Change the direction of ordering of categories shown by this widget.' + ) + ); + }); + + return parent::getCMSFields(); + } + + /** + * @return DataList + */ + public function getCategories() + { + $blog = $this->Blog(); + + if (!$blog) { + return array(); + } + + $query = $blog->Categories(); + + if ($this->Limit) { + $query = $query->limit(Convert::raw2sql($this->Limit)); + } + + if ($this->Order && $this->Direction) { + $query = $query->sort(Convert::raw2sql($this->Order), Convert::raw2sql($this->Direction)); + } + + return $query; + } +} + + diff --git a/src/Widgets/BlogCategoriesWidgetController.php b/src/Widgets/BlogCategoriesWidgetController.php new file mode 100644 index 0000000..b891e3a --- /dev/null +++ b/src/Widgets/BlogCategoriesWidgetController.php @@ -0,0 +1,14 @@ + 'Blog', + 'Blog' => Blog::class, ); /** @@ -74,7 +81,3 @@ class BlogRecentPostsWidget extends Widget return array(); } } - -class BlogRecentPostsWidget_Controller extends Widget_Controller -{ -} diff --git a/src/Widgets/BlogRecentPostsWidgetController.php b/src/Widgets/BlogRecentPostsWidgetController.php new file mode 100644 index 0000000..5c0cac0 --- /dev/null +++ b/src/Widgets/BlogRecentPostsWidgetController.php @@ -0,0 +1,14 @@ + 'Blog', + 'Blog' => Blog::class, ); /** @@ -46,8 +56,11 @@ class BlogTagsCloudWidget extends Widget * @var FieldList $fields */ $fields->push( - DropdownField::create('BlogID', _t('BlogTagsCloudWidget.Blog', - 'Blog'), Blog::get()->map()) + DropdownField::create( + 'BlogID', + _t('BlogTagsCloudWidget.Blog', 'Blog'), + Blog::get()->map() + ) ); }); @@ -105,7 +118,3 @@ class BlogTagsCloudWidget extends Widget return array(); } } - -class BlogTagsCloudWidget_Controller extends Widget_Controller -{ -} diff --git a/src/Widgets/BlogTagsCloudWidgetController.php b/src/Widgets/BlogTagsCloudWidgetController.php new file mode 100644 index 0000000..bfe2f24 --- /dev/null +++ b/src/Widgets/BlogTagsCloudWidgetController.php @@ -0,0 +1,13 @@ + 'Blog', + 'Blog' => Blog::class ); /** @@ -47,24 +56,44 @@ class BlogTagsWidget extends Widget { $this->beforeUpdateCMSFields(function (Fieldlist $fields) { $fields[] = DropdownField::create( - 'BlogID', _t('BlogTagsWidget.Blog', 'Blog'), Blog::get()->map() + 'BlogID', + _t('BlogTagsWidget.Blog', 'Blog'), + Blog::get()->map() ); $fields[] = NumericField::create( - 'Limit', _t('BlogTagsWidget.Limit.Label', 'Limit'), 0 + 'Limit', + _t('BlogTagsWidget.Limit.Label', 'Limit'), + 0 ) - ->setDescription(_t('BlogTagsWidget.Limit.Description', 'Limit the number of tags shown by this widget (set to 0 to show all tags).')) + ->setDescription( + _t( + 'BlogTagsWidget.Limit.Description', + 'Limit the number of tags shown by this widget (set to 0 to show all tags).' + ) + ) ->setMaxLength(3); $fields[] = DropdownField::create( - 'Order', _t('BlogTagsWidget.Sort.Label', 'Sort'), array('Title' => 'Title', 'Created' => 'Created', 'LastEdited' => 'Updated') + 'Order', + _t('BlogTagsWidget.Sort.Label', 'Sort'), + array('Title' => 'Title', 'Created' => 'Created', 'LastEdited' => 'Updated') ) - ->setDescription(_t('BlogTagsWidget.Sort.Description', 'Change the order of tags shown by this widget.')); + ->setDescription( + _t('BlogTagsWidget.Sort.Description', 'Change the order of tags shown by this widget.') + ); $fields[] = DropdownField::create( - 'Direction', _t('BlogTagsWidget.Direction.Label', 'Direction'), array('ASC' => 'Ascending', 'DESC' => 'Descending') + 'Direction', + _t('BlogTagsWidget.Direction.Label', 'Direction'), + array('ASC' => 'Ascending', 'DESC' => 'Descending') ) - ->setDescription(_t('BlogTagsWidget.Direction.Description', 'Change the direction of ordering of tags shown by this widget.')); + ->setDescription( + _t( + 'BlogTagsWidget.Direction.Description', + 'Change the direction of ordering of tags shown by this widget.' + ) + ); }); return parent::getCMSFields(); @@ -94,7 +123,3 @@ class BlogTagsWidget extends Widget return $query; } } - -class BlogTagsWidget_Controller extends Widget_Controller -{ -} diff --git a/src/Widgets/BlogTagsWidgetController.php b/src/Widgets/BlogTagsWidgetController.php new file mode 100644 index 0000000..07e6f11 --- /dev/null +++ b/src/Widgets/BlogTagsWidgetController.php @@ -0,0 +1,14 @@ + - <% if $Categories.exists %> - <%t Blog.PostedIn "Posted in" %> - <% loop $Categories %> - $Title<% if not Last %>, <% else %>;<% end_if %> - <% end_loop %> - <% end_if %> - - <% if $Tags.exists %> - <%t Blog.Tagged "Tagged" %> - <% loop $Tags %> - $Title<% if not Last %>, <% else %>;<% end_if %> - <% end_loop %> - <% end_if %> - - <% if $Comments.exists %> - - <%t Blog.Comments "Comments" %> - $Comments.count - ; - <% end_if %> - - <%t Blog.Posted "Posted" %> - $PublishDate.ago - - <% if $Credits %> - <%t Blog.By "by" %> - <% loop $Credits %><% if not $First && not $Last %>, <% end_if %><% if not $First && $Last %> <%t Blog.AND "and" %> <% end_if %><% if $URLSegment %>$Name.XML<% else %>$Name.XML<% end_if %><% end_loop %> - <% end_if %> - -

+

+ <% if $Categories.exists %> + <%t Blog.PostedIn "Posted in" %> + <% loop $Categories %> + $Title<% if not Last %>, <% else %>;<% end_if %> + <% end_loop %> + <% end_if %> + + <% if $Tags.exists %> + <%t Blog.Tagged "Tagged" %> + <% loop $Tags %> + $Title<% if not Last %>, <% else %>;<% end_if %> + <% end_loop %> + <% end_if %> + + <% if $Comments.exists %> + + <%t Blog.Comments "Comments" %> + $Comments.count + ; + <% end_if %> + + <%t Blog.Posted "Posted" %> + $PublishDate.ago + + <% if $Credits %> + <%t Blog.By "by" %> + + <% loop $Credits %> + <% if not $First && not $Last %>, <% end_if %> + <% if not $First && $Last %> <%t Blog.AND "and" %> <% end_if %> + <% if $URLSegment %> + $Name.XML + <% else %> + $Name.XML + <% end_if %> + <% end_loop %> + <% end_if %> +

diff --git a/templates/Includes/MemberDetails.ss b/templates/Includes/MemberDetails.ss index b968786..d423c48 100644 --- a/templates/Includes/MemberDetails.ss +++ b/templates/Includes/MemberDetails.ss @@ -3,7 +3,7 @@
<% if $CurrentProfile.BlogProfileImage %>
- $CurrentProfile.BlogProfileImage.setWidth(180) + $CurrentProfile.BlogProfileImage.ScaleWidth(180)
<% end_if %>
diff --git a/templates/Includes/PostSummary.ss b/templates/Includes/PostSummary.ss index 91a699a..b06d1f1 100644 --- a/templates/Includes/PostSummary.ss +++ b/templates/Includes/PostSummary.ss @@ -8,7 +8,7 @@

"> - $FeaturedImage.setWidth(795) + $FeaturedImage.ScaleWidth(795)

diff --git a/templates/BlogAdminSidebar_holder.ss b/templates/SilverStripe/Blog/Forms/BlogAdminSidebar_holder.ss similarity index 100% rename from templates/BlogAdminSidebar_holder.ss rename to templates/SilverStripe/Blog/Forms/BlogAdminSidebar_holder.ss diff --git a/templates/GridFieldAddByDBField.ss b/templates/SilverStripe/Blog/Forms/GridField/GridFieldAddByDBField.ss similarity index 100% rename from templates/GridFieldAddByDBField.ss rename to templates/SilverStripe/Blog/Forms/GridField/GridFieldAddByDBField.ss diff --git a/templates/forms/TabSet_holder.ss b/templates/SilverStripe/Blog/Forms/TabSet_holder.ss similarity index 93% rename from templates/forms/TabSet_holder.ss rename to templates/SilverStripe/Blog/Forms/TabSet_holder.ss index 85bc11c..dd2337d 100644 --- a/templates/forms/TabSet_holder.ss +++ b/templates/SilverStripe/Blog/Forms/TabSet_holder.ss @@ -1,4 +1,4 @@ -
- <%-- this resets ths template (from TabSet_holder) and renders using forTemplate --%> - $setTemplate('') -
\ No newline at end of file +
+ <%-- this resets ths template (from TabSet_holder) and renders using forTemplate --%> + $setTemplate('') +
diff --git a/templates/Layout/Blog.ss b/templates/SilverStripe/Blog/Model/Layout/Blog.ss similarity index 100% rename from templates/Layout/Blog.ss rename to templates/SilverStripe/Blog/Model/Layout/Blog.ss diff --git a/templates/Layout/BlogPost.ss b/templates/SilverStripe/Blog/Model/Layout/BlogPost.ss similarity index 81% rename from templates/Layout/BlogPost.ss rename to templates/SilverStripe/Blog/Model/Layout/BlogPost.ss index f3839dc..df0b7db 100644 --- a/templates/Layout/BlogPost.ss +++ b/templates/SilverStripe/Blog/Model/Layout/BlogPost.ss @@ -1,20 +1,20 @@ -<% require themedCSS('blog', 'blog') %> - -
-
-

$Title

- - <% if $FeaturedImage %> -

$FeaturedImage.setWidth(795)

- <% end_if %> - -
$Content
- - <% include EntryMeta %> -
- - $Form - $CommentsForm -
- -<% include BlogSideBar %> +<% require themedCSS('blog', 'blog') %> + +
+
+

$Title

+ + <% if $FeaturedImage %> +

$FeaturedImage.ScaleWidth(795)

+ <% end_if %> + +
$Content
+ + <% include EntryMeta %> +
+ + $Form + $CommentsForm +
+ +<% include BlogSideBar %> diff --git a/templates/Layout/Blog_profile.ss b/templates/SilverStripe/Blog/Model/Layout/Blog_profile.ss similarity index 100% rename from templates/Layout/Blog_profile.ss rename to templates/SilverStripe/Blog/Model/Layout/Blog_profile.ss diff --git a/tests/BlogCategoryTest.php b/tests/BlogCategoryTest.php index c29aa95..d9de583 100755 --- a/tests/BlogCategoryTest.php +++ b/tests/BlogCategoryTest.php @@ -1,5 +1,17 @@ logout(); } - $this->objFromFixture('BlogPost', 'FirstBlogPost'); + $this->objFromFixture(BlogPost::class, 'FirstBlogPost'); /** * @var BlogCategory $category */ - $category = $this->objFromFixture('BlogCategory', 'FirstCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'FirstCategory'); $this->assertEquals(5, $category->BlogPosts()->count(), 'Category blog post count'); } @@ -57,7 +69,7 @@ class BlogCategoryTest extends FunctionalTest */ public function testAllowMultibyteUrlSegment() { - $blog = $this->objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $cat = new BlogCategory(); $cat->BlogID = $blog->ID; $cat->Title = 'تست'; @@ -72,10 +84,10 @@ class BlogCategoryTest extends FunctionalTest { $this->useDraftSite(); - $this->objFromFixture('Member', 'Admin'); + $this->objFromFixture(Member::class, 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); - $category = $this->objFromFixture('BlogCategory', 'SecondCategory'); + $editor = $this->objFromFixture(Member::class, 'Editor'); + $category = $this->objFromFixture(BlogCategory::class, 'SecondCategory'); $this->assertFalse($category->canView($editor), 'Editor should not be able to view category.'); } @@ -87,20 +99,20 @@ class BlogCategoryTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $category = $this->objFromFixture('BlogCategory', 'FirstCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'FirstCategory'); $this->assertTrue($category->canEdit($admin), 'Admin should be able to edit category.'); $this->assertTrue($category->canEdit($editor), 'Editor should be able to edit category.'); - $category = $this->objFromFixture('BlogCategory', 'SecondCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'SecondCategory'); $this->assertTrue($category->canEdit($admin), 'Admin should be able to edit category.'); $this->assertFalse($category->canEdit($editor), 'Editor should not be able to edit category.'); - $category = $this->objFromFixture('BlogCategory', 'ThirdCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'ThirdCategory'); $this->assertTrue($category->canEdit($admin), 'Admin should always be able to edit category.'); $this->assertTrue($category->canEdit($editor), 'Editor should be able to edit category.'); @@ -110,10 +122,10 @@ class BlogCategoryTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $category = singleton('BlogCategory'); + $category = singleton(BlogCategory::class); $this->assertTrue($category->canCreate($admin), 'Admin should be able to create category.'); $this->assertTrue($category->canCreate($editor), 'Editor should be able to create category.'); @@ -123,24 +135,25 @@ class BlogCategoryTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $category = $this->objFromFixture('BlogCategory', 'FirstCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'FirstCategory'); $this->assertTrue($category->canDelete($admin), 'Admin should be able to delete category.'); $this->assertTrue($category->canDelete($editor), 'Editor should be able to category category.'); - $category = $this->objFromFixture('BlogCategory', 'SecondCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'SecondCategory'); $this->assertTrue($category->canDelete($admin), 'Admin should be able to delete category.'); $this->assertFalse($category->canDelete($editor), 'Editor should not be able to delete category.'); - $category = $this->objFromFixture('BlogCategory', 'ThirdCategory'); + $category = $this->objFromFixture(BlogCategory::class, 'ThirdCategory'); $this->assertTrue($category->canDelete($admin), 'Admin should always be able to delete category.'); $this->assertTrue($category->canDelete($editor), 'Editor should be able to delete category.'); } - public function testDuplicateCategories() { + public function testDuplicateCategories() + { $blog = new Blog(); $blog->Title = 'Testing for duplicate categories'; $blog->write(); @@ -148,18 +161,20 @@ class BlogCategoryTest extends FunctionalTest $category = new BlogCategory(); $category->Title = 'Test'; $category->BlogID = $blog->ID; + $category->URLSegment = 'test'; $category->write(); $category = new BlogCategory(); $category->Title = 'Test'; + $category->URLSegment = 'test'; $category->BlogID = $blog->ID; try { $category->write(); $this->fail('Duplicate BlogCategory written'); } catch (ValidationException $e) { - $codeList = $e->getResult()->codeList(); - $this->assertCount(1, $codeList); - $this->assertEquals(BlogTag::DUPLICATE_EXCEPTION, $codeList[0]); + $messages = $e->getResult()->getMessages(); + $this->assertCount(1, $messages); + $this->assertEquals(BlogTag::DUPLICATE_EXCEPTION, $messages[0]['messageType']); } } } diff --git a/tests/BlogPostFilterTest.php b/tests/BlogPostFilterTest.php index e878e40..c78fbc9 100755 --- a/tests/BlogPostFilterTest.php +++ b/tests/BlogPostFilterTest.php @@ -1,29 +1,41 @@ objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $this->assertEquals(3, $blog->AllChildren()->Count(), 'Filtered blog posts'); - SS_Datetime::set_mock_now('2020-01-01 00:00:00'); + DBDatetime::set_mock_now('2020-01-01 00:00:00'); $this->assertEquals(5, $blog->AllChildren()->Count(), 'Unfiltered blog posts'); } diff --git a/tests/BlogPostNotificationsTest.php b/tests/BlogPostNotificationsTest.php index 25f4073..4129388 100644 --- a/tests/BlogPostNotificationsTest.php +++ b/tests/BlogPostNotificationsTest.php @@ -1,11 +1,17 @@ markTestSkipped('Comments Notification module is not installed'); } - $blogPost = $this->objFromFixture('BlogPost', 'PostC'); - $comment = new Comment(); + $blogPost = $this->objFromFixture(BlogPost::class, 'PostC'); + $comment = new \SilverStripe\Comments\Model\Comment(); $comment->Comment = 'This is a comment'; $comment->write(); $recipients = $blogPost->notificationRecipients( @@ -27,8 +33,10 @@ class BlogPostNotificationsTest extends SapphireTest } sort($segments); - $this->assertEquals(array('blog-contributor', 'blog-editor', - 'blog-writer', ), $segments); + $this->assertEquals( + array('blog-contributor', 'blog-editor', 'blog-writer'), + $segments + ); } public function testUpdateNotificationSubject() @@ -36,8 +44,8 @@ class BlogPostNotificationsTest extends SapphireTest if (!class_exists('CommentNotifier')) { $this->markTestSkipped('Comments Notification module is not installed'); } - $blogPost = $this->objFromFixture('BlogPost', 'PostC'); - $comment = new Comment(); + $blogPost = $this->objFromFixture(BlogPost::class, 'PostC'); + $comment = new SilverStripe\Comments\Model\Comment(); $comment->Comment = 'This is a comment'; $comment->write(); $recipients = $blogPost->notificationRecipients( diff --git a/tests/BlogPostTest.php b/tests/BlogPostTest.php index c527604..09bacba 100644 --- a/tests/BlogPostTest.php +++ b/tests/BlogPostTest.php @@ -1,26 +1,27 @@ objFromFixture('Member', $user); - $pageRecord = $this->objFromFixture('BlogPost', $page); - SS_Datetime::set_mock_now($date); + $userRecord = $this->objFromFixture(Member::class, $user); + $pageRecord = $this->objFromFixture(BlogPost::class, $page); + DBDatetime::set_mock_now($date); $this->assertEquals($canView, $pageRecord->canView($userRecord)); } + /** + * @return array + */ public function canViewProvider() { $someFutureDate = '2013-10-10 20:00:00'; @@ -70,12 +74,12 @@ class BlogPostTest extends SapphireTest public function testCandidateAuthors() { - $blogpost = $this->objFromFixture('BlogPost', 'PostC'); + $blogpost = $this->objFromFixture(BlogPost::class, 'PostC'); $this->assertEquals(7, $blogpost->getCandidateAuthors()->count()); //Set the group to draw Members from - Config::inst()->update('BlogPost', 'restrict_authors_to_group', 'blogusers'); + Config::inst()->update(BlogPost::class, 'restrict_authors_to_group', 'blogusers'); $this->assertEquals(3, $blogpost->getCandidateAuthors()->count()); @@ -86,12 +90,12 @@ class BlogPostTest extends SapphireTest public function testCanViewFuturePost() { - $blogPost = $this->objFromFixture('BlogPost', 'NullPublishDate'); + $blogPost = $this->objFromFixture(BlogPost::class, 'NullPublishDate'); - $editor = $this->objFromFixture('Member', 'BlogEditor'); + $editor = $this->objFromFixture(Member::class, 'BlogEditor'); $this->assertTrue($blogPost->canView($editor)); - $visitor = $this->objFromFixture('Member', 'Visitor'); + $visitor = $this->objFromFixture(Member::class, 'Visitor'); $this->assertFalse($blogPost->canView($visitor)); } @@ -101,10 +105,10 @@ class BlogPostTest extends SapphireTest */ public function testGetDate() { - $blogPost = $this->objFromFixture('BlogPost', 'NullPublishDate'); + $blogPost = $this->objFromFixture(BlogPost::class, 'NullPublishDate'); $this->assertNull($blogPost->getDate()); - $blogPost = $this->objFromFixture('BlogPost', 'PostA'); + $blogPost = $this->objFromFixture(BlogPost::class, 'PostA'); $this->assertEquals('2012-01-09 15:00:00', $blogPost->getDate()); } } diff --git a/tests/BlogTagTest.php b/tests/BlogTagTest.php index dcf886c..464a918 100755 --- a/tests/BlogTagTest.php +++ b/tests/BlogTagTest.php @@ -1,14 +1,26 @@ logout(); } - $this->objFromFixture('BlogPost', 'FirstBlogPost'); + $this->objFromFixture(BlogPost::class, 'FirstBlogPost'); /** * @var BlogTag $tag */ - $tag = $this->objFromFixture('BlogTag', 'FirstTag'); + $tag = $this->objFromFixture(BlogTag::class, 'FirstTag'); $this->assertEquals(1, $tag->BlogPosts()->count(), 'Tag blog post count'); } @@ -57,7 +69,7 @@ class BlogTagTest extends FunctionalTest */ public function testAllowMultibyteUrlSegment() { - $blog = $this->objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $tag = new BlogTag(); $tag->BlogID = $blog->ID; $tag->Title = 'تست'; @@ -75,15 +87,15 @@ class BlogTagTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $tag = $this->objFromFixture('BlogTag', 'FirstTag'); + $tag = $this->objFromFixture(BlogTag::class, 'FirstTag'); $this->assertTrue($tag->canView($admin), 'Admin should be able to view tag.'); $this->assertTrue($tag->canView($editor), 'Editor should be able to view tag.'); - $tag = $this->objFromFixture('BlogTag', 'SecondTag'); + $tag = $this->objFromFixture(BlogTag::class, 'SecondTag'); $this->assertTrue($tag->canView($admin), 'Admin should be able to view tag.'); $this->assertFalse($tag->canView($editor), 'Editor should not be able to view tag.'); @@ -93,20 +105,20 @@ class BlogTagTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $tag = $this->objFromFixture('BlogTag', 'FirstTag'); + $tag = $this->objFromFixture(BlogTag::class, 'FirstTag'); $this->assertTrue($tag->canEdit($admin), 'Admin should be able to edit tag.'); $this->assertTrue($tag->canEdit($editor), 'Editor should be able to edit tag.'); - $tag = $this->objFromFixture('BlogTag', 'SecondTag'); + $tag = $this->objFromFixture(BlogTag::class, 'SecondTag'); $this->assertTrue($tag->canEdit($admin), 'Admin should be able to edit tag.'); $this->assertFalse($tag->canEdit($editor), 'Editor should not be able to edit tag.'); - $tag = $this->objFromFixture('BlogTag', 'ThirdTag'); + $tag = $this->objFromFixture(BlogTag::class, 'ThirdTag'); $this->assertTrue($tag->canEdit($admin), 'Admin should always be able to edit tags.'); $this->assertTrue($tag->canEdit($editor), 'Editor should be able to edit tag.'); @@ -116,10 +128,10 @@ class BlogTagTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $tag = singleton('BlogTag'); + $tag = singleton(BlogTag::class); $this->assertTrue($tag->canCreate($admin), 'Admin should be able to create tag.'); $this->assertTrue($tag->canCreate($editor), 'Editor should be able to create tag.'); @@ -129,26 +141,27 @@ class BlogTagTest extends FunctionalTest { $this->useDraftSite(); - $admin = $this->objFromFixture('Member', 'Admin'); - $editor = $this->objFromFixture('Member', 'Editor'); + $admin = $this->objFromFixture(Member::class, 'Admin'); + $editor = $this->objFromFixture(Member::class, 'Editor'); - $tag = $this->objFromFixture('BlogTag', 'FirstTag'); + $tag = $this->objFromFixture(BlogTag::class, 'FirstTag'); $this->assertTrue($tag->canDelete($admin), 'Admin should be able to delete tag.'); $this->assertTrue($tag->canDelete($editor), 'Editor should be able to delete tag.'); - $tag = $this->objFromFixture('BlogTag', 'SecondTag'); + $tag = $this->objFromFixture(BlogTag::class, 'SecondTag'); $this->assertTrue($tag->canDelete($admin), 'Admin should be able to delete tag.'); $this->assertFalse($tag->canDelete($editor), 'Editor should not be able to delete tag.'); - $tag = $this->objFromFixture('BlogTag', 'ThirdTag'); + $tag = $this->objFromFixture(BlogTag::class, 'ThirdTag'); $this->assertTrue($tag->canDelete($admin), 'Admin should always be able to delete tags.'); $this->assertTrue($tag->canDelete($editor), 'Editor should be able to delete tag.'); } - public function testDuplicateTagsForURLSegment() { + public function testDuplicateTagsForURLSegment() + { $blog = new Blog(); $blog->Title = 'Testing for duplicates blog'; $blog->write(); @@ -162,11 +175,11 @@ class BlogTagTest extends FunctionalTest $tag2->Title = 'cat test'; $tag2->BlogID = $blog->ID; $tag2->write(); - $this->assertEquals('cat-test-0', $tag2->URLSegment); - + $this->assertEquals('cat-test-1', $tag2->URLSegment); } - public function testDuplicateTags() { + public function testDuplicateTags() + { $blog = new Blog(); $blog->Title = 'Testing for duplicate tags'; $blog->write(); @@ -174,19 +187,20 @@ class BlogTagTest extends FunctionalTest $tag = new BlogTag(); $tag->Title = 'Test'; $tag->BlogID = $blog->ID; + $tag->URLSegment = 'test'; $tag->write(); $tag = new BlogTag(); $tag->Title = 'Test'; + $tag->URLSegment = 'test'; $tag->BlogID = $blog->ID; try { $tag->write(); $this->fail('Duplicate BlogTag written'); } catch (ValidationException $e) { - $codeList = $e->getResult()->codeList(); - $this->assertCount(1, $codeList); - $this->assertEquals(BlogTag::DUPLICATE_EXCEPTION, $codeList[0]); + $messages = $e->getResult()->getMessages(); + $this->assertCount(1, $messages); + $this->assertEquals(BlogTag::DUPLICATE_EXCEPTION, $messages[0]['messageType']); } } - } diff --git a/tests/BlogTagsCloudWidgetTest.php b/tests/BlogTagsCloudWidgetTest.php index d37a2bc..315706e 100644 --- a/tests/BlogTagsCloudWidgetTest.php +++ b/tests/BlogTagsCloudWidgetTest.php @@ -1,14 +1,23 @@ markTestSkipped('Widgets module not installed'); } @@ -23,12 +32,13 @@ class BlogTagsCloudWidgetTest extends SapphireTest { $this->assertEquals($expected, $names); } - public function testGetTags() { - if (!class_exists('Widget')) { + public function testGetTags() + { + if (!class_exists('SilverStripe\\Widgets\\Model\\Widget')) { $this->markTestSkipped('Widgets module not installed'); } $widget = new BlogTagsCloudWidget(); - $blog = $this->objFromFixture('Blog', 'FourthBlog'); + $blog = $this->objFromFixture(Blog::class, 'FourthBlog'); $widget->BlogID = $blog->ID; $widget->write(); $tags = $widget->getTags()->toArray(); diff --git a/tests/BlogTest.php b/tests/BlogTest.php index bfe63fb..386769b 100755 --- a/tests/BlogTest.php +++ b/tests/BlogTest.php @@ -1,5 +1,21 @@ objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $blog->publish('Stage', 'Live'); } @@ -33,7 +49,7 @@ class BlogTest extends SapphireTest */ public function tearDown() { - SS_Datetime::clear_mock_now(); + DBDatetime::clear_mock_now(); Config::unnest(); parent::tearDown(); @@ -50,17 +66,17 @@ class BlogTest extends SapphireTest /** * @var Blog $blog */ - $blog = $this->objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); - Config::inst()->update('BlogPost', 'show_in_sitetree', true); + Config::inst()->update(BlogPost::class, 'show_in_sitetree', true); $classes = $blog->getExcludedSiteTreeClassNames(); - $this->assertNotContains('BlogPost', $classes, 'BlogPost class should be hidden.'); + $this->assertNotContains(BlogPost::class, $classes, 'BlogPost class should be hidden.'); - Config::inst()->update('BlogPost', 'show_in_sitetree', false); + Config::inst()->update(BlogPost::class, 'show_in_sitetree', false); $classes = $blog->getExcludedSiteTreeClassNames(); - $this->assertContains('BlogPost', $classes, 'BlogPost class should be hidden.'); + $this->assertContains(BlogPost::class, $classes, 'BlogPost class should be hidden.'); } public function testGetArchivedBlogPosts() @@ -74,7 +90,7 @@ class BlogTest extends SapphireTest /** * @var Blog $blog */ - $blog = $this->objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $archive = $blog->getArchivedBlogPosts(2013); @@ -96,7 +112,7 @@ class BlogTest extends SapphireTest /** * @var Blog $blog */ - $blog = $this->objFromFixture('Blog', 'FirstBlog'); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); $link = Controller::join_links($blog->Link('archive'), '2013', '10', '01'); @@ -135,8 +151,8 @@ class BlogTest extends SapphireTest */ public function testArchiveYear() { - $blog = $this->objFromFixture('Blog', 'FirstBlog'); - $controller = new Blog_Controller($blog); + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); + $controller = new BlogController($blog); $this->requestURL($controller, 'first-post/archive/'); $this->assertEquals(2013, $controller->getArchiveYear(), 'getArchiveYear should return 2013'); } @@ -156,47 +172,47 @@ class BlogTest extends SapphireTest /** * @var Blog $firstBlog */ - $firstBlog = $this->objFromFixture('Blog', 'FirstBlog'); + $firstBlog = $this->objFromFixture(Blog::class, 'FirstBlog'); /** * @var Blog $fourthBlog */ - $fourthBlog = $this->objFromFixture('Blog', 'FourthBlog'); + $fourthBlog = $this->objFromFixture(Blog::class, 'FourthBlog'); /** * @var BlogPost $postA */ - $postA = $this->objFromFixture('BlogPost', 'PostA'); + $postA = $this->objFromFixture(BlogPost::class, 'PostA'); /** * @var BlogPost $postB */ - $postB = $this->objFromFixture('BlogPost', 'PostB'); + $postB = $this->objFromFixture(BlogPost::class, 'PostB'); /** * @var BlogPost $postC */ - $postC = $this->objFromFixture('BlogPost', 'PostC'); + $postC = $this->objFromFixture(BlogPost::class, 'PostC'); /** * @var Member $editor */ - $editor = $this->objFromFixture('Member', 'BlogEditor'); + $editor = $this->objFromFixture(Member::class, 'BlogEditor'); /** * @var Member $writer */ - $writer = $this->objFromFixture('Member', 'Writer'); + $writer = $this->objFromFixture(Member::class, 'Writer'); /** * @var Member $contributor */ - $contributor = $this->objFromFixture('Member', 'Contributor'); + $contributor = $this->objFromFixture(Member::class, 'Contributor'); /** * @var Member $visitor */ - $visitor = $this->objFromFixture('Member', 'Visitor'); + $visitor = $this->objFromFixture(Member::class, 'Visitor'); $this->assertEquals('Editor', $fourthBlog->RoleOf($editor)); $this->assertEquals('Contributor', $fourthBlog->RoleOf($contributor)); @@ -274,9 +290,9 @@ class BlogTest extends SapphireTest public function testFilteredCategories() { - $blog = $this->objFromFixture('Blog', 'FirstBlog'); - $controller = new Blog_Controller($blog); - + $blog = $this->objFromFixture(Blog::class, 'FirstBlog'); + $controller = new BlogController($blog); + // Root url $this->requestURL($controller, 'first-post'); $this->assertIDsEquals( @@ -293,10 +309,10 @@ class BlogTest extends SapphireTest ); // Posts - $firstPostID = $this->idFromFixture('BlogPost', 'FirstBlogPost'); - $secondPostID = $this->idFromFixture('BlogPost', 'SecondBlogPost'); - $firstFuturePostID = $this->idFromFixture('BlogPost', 'FirstFutureBlogPost'); - $secondFuturePostID = $this->idFromFixture('BlogPost', 'SecondFutureBlogPost'); + $firstPostID = $this->idFromFixture(BlogPost::class, 'FirstBlogPost'); + $secondPostID = $this->idFromFixture(BlogPost::class, 'SecondBlogPost'); + $firstFuturePostID = $this->idFromFixture(BlogPost::class, 'FirstFutureBlogPost'); + $secondFuturePostID = $this->idFromFixture(BlogPost::class, 'SecondFutureBlogPost'); // Request first tag $this->requestURL($controller, 'first-post/tag/first-tag'); @@ -321,10 +337,10 @@ class BlogTest extends SapphireTest */ protected function requestURL(ContentController $controller, $url) { - $request = new SS_HTTPRequest('get', $url); + $request = new HTTPRequest('get', $url); $request->match('$URLSegment//$Action/$ID/$OtherID'); $request->shift(); - $controller->init(); + $controller->doInit(); $controller->handleRequest($request, new DataModel()); } diff --git a/tests/blog.yml b/tests/blog.yml index e664f88..79a09f3 100755 --- a/tests/blog.yml +++ b/tests/blog.yml @@ -1,6 +1,6 @@ # Mock date is set to 2013-10-01 20:00:00 -Group: +SilverStripe\Security\Group: Administrators: Title: Administrators Editors: @@ -9,193 +9,193 @@ Group: Title: Blog Users Code: blogusers -Permission: +SilverStripe\Security\Permission: Administrators: Code: ADMIN - Group: =>Group.Administrators + Group: =>SilverStripe\Security\Group.Administrators Editors: Code: CMS_ACCESS_CMSMain - Group: =>Group.Editors + Group: =>SilverStripe\Security\Group.Editors BlogUsers: Code: CMS_ACCESS_CMSMain - Group: =>Group.BlogUsers + Group: =>SilverStripe\Security\Group.BlogUsers -SiteConfig: +SilverStripe\SiteConfig\SiteConfig: Default: CanEditType: 'OnlyTheseUsers' CanCreateTopLevelType: 'OnlyTheseUsers' - EditorGroups: =>Group.Administrators,=>Group.Editors - CreateTopLevelGroups: =>Group.Administrators,=>Group.Editors + EditorGroups: =>SilverStripe\Security\Group.Administrators,=>SilverStripe\Security\Group.Editors + CreateTopLevelGroups: =>SilverStripe\Security\Group.Administrators,=>SilverStripe\Security\Group.Editors -Member: +SilverStripe\Security\Member: Admin: FirstName: Test Surname: Administrator - Groups: =>Group.Administrators + Groups: =>SilverStripe\Security\Group.Administrators Editor: FirstName: Test Surname: Editor - Groups: =>Group.Editors + Groups: =>SilverStripe\Security\Group.Editors BlogEditor: FirstName: Blog Surname: Editor - Groups: =>Group.BlogUsers + Groups: =>SilverStripe\Security\Group.BlogUsers Writer: FirstName: Blog Surname: Writer - Groups: =>Group.BlogUsers + Groups: =>SilverStripe\Security\Group.BlogUsers Contributor: FirstName: Blog Surname: Contributor - Groups: =>Group.BlogUsers + Groups: =>SilverStripe\Security\Group.BlogUsers Visitor: FirstName: Blog Surname: Visitor -Blog: +SilverStripe\Blog\Model\Blog: FirstBlog: Title: 'First Blog' SecondBlog: Title: 'Second Blog' CanViewType: 'OnlyTheseUsers' CanEditType: 'OnlyTheseUsers' - ViewerGroups: =>Group.Administrators - EditorGroups: =>Group.Administrators + ViewerGroups: =>SilverStripe\Security\Group.Administrators + EditorGroups: =>SilverStripe\Security\Group.Administrators ThirdBlog: Title: 'Third Blog' CanEditType: 'OnlyTheseUsers' - EditorGroups: =>Group.Editors + EditorGroups: =>SilverStripe\Security\Group.Editors FourthBlog: Title: 'Fourth Blog' - Editors: =>Member.BlogEditor - Writers: =>Member.Writer - Contributors: =>Member.Contributor + Editors: =>SilverStripe\Security\Member.BlogEditor + Writers: =>SilverStripe\Security\Member.Writer + Contributors: =>SilverStripe\Security\Member.Contributor -BlogTag: +SilverStripe\Blog\Model\BlogTag: FirstTag: Title: 'First Tag' URLSegment: 'first-tag' - Blog: =>Blog.FirstBlog + Blog: =>SilverStripe\Blog\Model\Blog.FirstBlog SecondTag: Title: 'Second Tag' URLSegment: 'second-tag' - Blog: =>Blog.SecondBlog + Blog: =>SilverStripe\Blog\Model\Blog.SecondBlog ThirdTag: Title: 'Third Tag' URLSegment: 'third-tag' - Blog: =>Blog.ThirdBlog + Blog: =>SilverStripe\Blog\Model\Blog.ThirdBlog #Tags for Tag Cloud widget PopularTag: Title: 'Popular' URLSegment: 'popular-tag' - Blog: =>Blog.FourthBlog + Blog: =>SilverStripe\Blog\Model\Blog.FourthBlog CoolTag: Title: 'Cool' URLSegment: 'cool-tag' - Blog: =>Blog.FourthBlog + Blog: =>SilverStripe\Blog\Model\Blog.FourthBlog CatTag: Title: 'Cat' URLSegment: 'cat-tag' - Blog: =>Blog.FourthBlog + Blog: =>SilverStripe\Blog\Model\Blog.FourthBlog KiwiTag: Title: 'Kiwi' URLSegment: 'kiwi-tag' - Blog: =>Blog.FourthBlog + Blog: =>SilverStripe\Blog\Model\Blog.FourthBlog -BlogCategory: +SilverStripe\Blog\Model\BlogCategory: FirstCategory: Title: 'First Category' URLSegment: 'first-category' - Blog: =>Blog.FirstBlog + Blog: =>SilverStripe\Blog\Model\Blog.FirstBlog SecondCategory: Title: 'Second Category' URLSegment: 'second-category' - Blog: =>Blog.SecondBlog + Blog: =>SilverStripe\Blog\Model\Blog.SecondBlog ThirdCategory: Title: 'Third Category' URLSegment: 'third-category' - Blog: =>Blog.ThirdBlog + Blog: =>SilverStripe\Blog\Model\Blog.ThirdBlog -BlogPost: +SilverStripe\Blog\Model\BlogPost: FirstBlogPost: Title: 'First Post' URLSegment: first-post PublishDate: '2013-10-01 15:00:00' - Parent: =>Blog.FirstBlog - Tags: =>BlogTag.FirstTag - Categories: =>BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FirstBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.FirstTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory SecondBlogPost: Title: 'Second Post' URLSegment: second-post PublishDate: '2013-09-01 15:00:00' - Parent: =>Blog.FirstBlog + Parent: =>SilverStripe\Blog\Model\Blog.FirstBlog ThirdBlogPost: Title: 'Old Post' URLSegment: old-post PublishDate: '2012-01-09 15:00:00' - Parent: =>Blog.FirstBlog + Parent: =>SilverStripe\Blog\Model\Blog.FirstBlog FirstFutureBlogPost: Title: 'Future Post' URLSegment: future-post PublishDate: '2015-01-01 00:00:00' - Tags: =>BlogTag.FirstTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FirstBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.FirstTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FirstBlog SecondFutureBlogPost: Title: 'Future Post 2' URLSegment: future-post-2 PublishDate: '2013-11-01 00:00:00' - Tags: =>BlogTag.FirstTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FirstBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.FirstTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FirstBlog PostA: Title: 'One Post' PublishDate: '2012-01-09 15:00:00' - Parent: =>Blog.FourthBlog - Authors: =>Member.Writer,=>Member.Contributor + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog + Authors: =>SilverStripe\Security\Member.Writer,=>SilverStripe\Security\Member.Contributor PostB: Title: 'Second Post' PublishDate: '2012-01-09 15:00:00' - Parent: =>Blog.FourthBlog - Authors: =>Member.BlogEditor + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog + Authors: =>SilverStripe\Security\Member.BlogEditor PostC: Title: 'Third Post' PublishDate: '2012-01-09 15:00:00' - Parent: =>Blog.FourthBlog - Authors: =>Member.BlogEditor,=>Member.Writer,=>Member.Contributor + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog + Authors: =>SilverStripe\Security\Member.BlogEditor,=>SilverStripe\Security\Member.Writer,=>SilverStripe\Security\Member.Contributor NullPublishDate: Title: 'No publish date' - PublishDate: 'NULL' - Parent: =>Blog.FourthBlog - Authors: =>Member.BlogEditor,=>Member.Writer,=>Member.Contributor + PublishDate: '' + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog + Authors: =>SilverStripe\Security\Member.BlogEditor,=>SilverStripe\Security\Member.Writer,=>SilverStripe\Security\Member.Contributor #Posts for the tag cloud widget test TaggedPost1: Title: 'Tagged Post 1' URLSegment: tagged-post-1 PublishDate: '2012-01-09 15:00:00' - Tags: =>BlogTag.PopularTag,=>BlogTag.CoolTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FourthBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.PopularTag,=>SilverStripe\Blog\Model\BlogTag.CoolTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog TaggedPost2: Title: 'Tagged Post 2' URLSegment: tagged-post-2 PublishDate: '2012-01-09 15:00:00' - Tags: =>BlogTag.PopularTag,=>BlogTag.CoolTag,=>BlogTag.CatTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FourthBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.PopularTag,=>SilverStripe\Blog\Model\BlogTag.CoolTag,=>SilverStripe\Blog\Model\BlogTag.CatTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog TaggedPost3: Title: 'Tagged Post 3' URLSegment: tagged-post-3 PublishDate: '2012-01-09 17:20:00' - Tags: =>BlogTag.PopularTag,=>BlogTag.CoolTag,=>BlogTag.CatTag,=>BlogTag.KiwiTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FourthBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.PopularTag,=>SilverStripe\Blog\Model\BlogTag.CoolTag,=>SilverStripe\Blog\Model\BlogTag.CatTag,=>SilverStripe\Blog\Model\BlogTag.KiwiTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog TaggedPost4: Title: 'Tagged Post 4' URLSegment: tagged-post-4 PublishDate: '2012-04-09 15:00:00' - Tags: =>BlogTag.PopularTag - Categories: =>BlogCategory.FirstCategory - Parent: =>Blog.FourthBlog + Tags: =>SilverStripe\Blog\Model\BlogTag.PopularTag + Categories: =>SilverStripe\Blog\Model\BlogCategory.FirstCategory + Parent: =>SilverStripe\Blog\Model\Blog.FourthBlog