From c45499b8764320f1af023645c315ae900521a927 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Tue, 15 Oct 2019 15:35:47 +1300 Subject: [PATCH] Initial work on categorisation rewrite. Remove relation between blog and categories / tags Soft-resolve duplicates on query via URLSegment Remove and simplify tag / category management Code quality cleanup --- src/Admin/GridFieldCategorisationConfig.php | 45 +-- src/Model/Blog.php | 135 ++++--- src/Model/BlogCategory.php | 10 +- src/Model/BlogController.php | 391 +++++++++++--------- src/Model/BlogMemberExtension.php | 5 + src/Model/BlogObject.php | 42 ++- src/Model/BlogPost.php | 24 +- src/Model/BlogTag.php | 7 +- src/Model/CategorisationObject.php | 4 +- 9 files changed, 390 insertions(+), 273 deletions(-) diff --git a/src/Admin/GridFieldCategorisationConfig.php b/src/Admin/GridFieldCategorisationConfig.php index 05d36d9..6e9de8d 100644 --- a/src/Admin/GridFieldCategorisationConfig.php +++ b/src/Admin/GridFieldCategorisationConfig.php @@ -4,6 +4,7 @@ namespace SilverStripe\Blog\Admin; use SilverStripe\Blog\Forms\GridField\GridFieldAddByDBField; use SilverStripe\Blog\Model\CategorisationObject; +use SilverStripe\CMS\Model\SiteTree; use SilverStripe\Forms\GridField\GridFieldAddNewButton; use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor; use SilverStripe\Forms\GridField\GridFieldDataColumns; @@ -12,13 +13,14 @@ use SilverStripe\ORM\SS_List; class GridFieldCategorisationConfig extends GridFieldConfig_RecordEditor { /** - * @param int $itemsPerPage + * @param int $itemsPerPage * @param array|SS_List $mergeRecords - * @param string $parentType - * @param string $parentMethod - * @param string $childMethod + * @param string $parentType + * @param string $parentMethod + * @param string $childMethod + * @param SiteTree $parent */ - public function __construct($itemsPerPage, $mergeRecords, $parentType, $parentMethod, $childMethod) + public function __construct($itemsPerPage, $mergeRecords, $parentType, $parentMethod, $childMethod, $parent) { parent::__construct($itemsPerPage); @@ -39,31 +41,24 @@ class GridFieldCategorisationConfig extends GridFieldConfig_RecordEditor $columns->setFieldFormatting( [ - 'BlogPostsCount' => function ($value, CategorisationObject $item) { + 'BlogPostsCount' => function ($value, CategorisationObject $item) use ($parent) { + return $item + ->BlogPosts() + ->filter(['ParentID' => $parent->ID]) + ->Count(); + }, + 'BlogPostsAllCount' => 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::class); - $columns->setDisplayFields( [ - 'Title' => _t(__CLASS__ . '.Title', 'Title'), - 'BlogPostsCount' => _t(__CLASS__ . '.Posts', 'Posts'), - 'MergeAction' => 'MergeAction', - 'Actions' => 'Actions' + 'Title' => _t(__CLASS__ . '.Title', 'Title'), + 'BlogPostsCount' => _t(__CLASS__ . '.PostsThisBlog', 'Posts (This Blog)'), + 'BlogPostsAllCount' => _t(__CLASS__ . '.PostsAllBlogs', 'Posts (All Blogs)'), + 'MergeAction' => 'MergeAction', + 'Actions' => 'Actions' ] ); } diff --git a/src/Model/Blog.php b/src/Model/Blog.php index 7d8df6d..eaf33a3 100644 --- a/src/Model/Blog.php +++ b/src/Model/Blog.php @@ -2,10 +2,10 @@ namespace SilverStripe\Blog\Model; +use Exception; use Page; use SilverStripe\Blog\Admin\GridFieldCategorisationConfig; use SilverStripe\Blog\Forms\GridField\GridFieldConfigBlogPost; -use SilverStripe\CMS\Controllers\RootURLController; use SilverStripe\Control\Controller; use SilverStripe\Core\Convert; use SilverStripe\Forms\FieldList; @@ -14,13 +14,15 @@ use SilverStripe\Forms\GridField\GridFieldConfig; use SilverStripe\Forms\ListboxField; use SilverStripe\Forms\LiteralField; use SilverStripe\Forms\NumericField; +use SilverStripe\Forms\Tab; +use SilverStripe\Forms\TabSet; use SilverStripe\ORM\DataList; use SilverStripe\ORM\DataObject; use SilverStripe\ORM\DB; -use SilverStripe\ORM\HasManyList; use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\SS_List; use SilverStripe\ORM\UnsavedRelationList; +use SilverStripe\ORM\ValidationException; use SilverStripe\Security\Group; use SilverStripe\Security\Member; use SilverStripe\Security\Permission; @@ -31,8 +33,7 @@ use SilverStripe\View\Requirements; /** * Blog Holder * - * @method HasManyList Tags() List of tags in this blog - * @method HasManyList Categories() List of categories in this blog + * @property int $PostsPerPage * @method ManyManyList Editors() List of editors * @method ManyManyList Writers() List of writers * @method ManyManyList Contributors() List of contributors @@ -88,20 +89,12 @@ class Blog extends Page implements PermissionProvider 'PostsPerPage' => 'Int', ]; - /** - * @var array - */ - private static $has_many = [ - 'Tags' => BlogTag::class, - 'Categories' => BlogCategory::class, - ]; - /** * @var array */ private static $many_many = [ - 'Editors' => Member::class, - 'Writers' => Member::class, + 'Editors' => Member::class, + 'Writers' => Member::class, 'Contributors' => Member::class, ]; @@ -134,6 +127,52 @@ class Blog extends Page implements PermissionProvider private static $icon_class = 'font-icon-p-posts'; + /** + * Gets the list of all tags attached to this blog + * + * @param bool $hideEmpty Set to false to include tags without posts + * @return DataList|BlogTag[] + */ + public function Tags($hideEmpty = true) + { + $tags = BlogTag::get(); + if ($this->ID) { + $tags->setDataQueryParam('BlogID', $this->ID); + + // Conditionally hide empty tags + if ($hideEmpty) { + $tags = $tags->filter([ + 'BlogPosts.ParentID' => $this->ID, + ]); + } + } + $this->extend('updateBlogTags', $tags); + return $tags; + } + + /** + * Gets the list of all categories attached to this blog + * + * @param bool $hideEmpty Set to false to include categories without posts + * @return DataList|BlogCategory[] + */ + public function Categories($hideEmpty = true) + { + $tags = BlogCategory::get(); + if ($this->ID) { + $tags->setDataQueryParam('BlogID', $this->ID); + + // Conditionally hide empty categories + if ($hideEmpty) { + $tags = $tags->filter([ + 'BlogPosts.ParentID' => $this->ID, + ]); + } + } + $this->extend('updateBlogCategories', $tags); + return $tags; + } + /** * {@inheritdoc} */ @@ -141,7 +180,7 @@ class Blog extends Page implements PermissionProvider { $this->addCMSRequirements(); - $this->beforeUpdateCMSFields(function ($fields) { + $this->beforeUpdateCMSFields(function (FieldList $fields) { if (!$this->canEdit()) { return; } @@ -149,43 +188,47 @@ class Blog extends Page implements PermissionProvider $categories = GridField::create( 'Categories', _t(__CLASS__ . '.Categories', 'Categories'), - $this->Categories(), + $this->Categories(false), GridFieldCategorisationConfig::create( 15, - $this->Categories()->sort('Title'), + $this->Categories(false)->sort('Title'), BlogCategory::class, 'Categories', - 'BlogPosts' + 'BlogPosts', + $this ) ); $tags = GridField::create( 'Tags', _t(__CLASS__ . '.Tags', 'Tags'), - $this->Tags(), + $this->Tags(false), GridFieldCategorisationConfig::create( 15, - $this->Tags()->sort('Title'), + $this->Tags(false)->sort('Title'), BlogTag::class, 'Tags', - 'BlogPosts' + 'BlogPosts', + $this ) ); - /** - * @var FieldList $fields - */ - $fields->addFieldsToTab( + $fields->addFieldToTab( + 'Root', + TabSet::create('Categorisation', _t(__CLASS__ . '.Categorisation', 'Categorisation')) + ->addExtraClass('blog-cms-categorisation') + ); + $fields->addFieldToTab( 'Root.Categorisation', - [ - $categories, - $tags - ] + Tab::create('Categories', _t(__CLASS__ . '.Categories', 'Categories')) + ); + $fields->addFieldToTab( + 'Root.Categorisation', + Tab::create('Tags', _t(__CLASS__ . '.Tags', 'Tags')) ); - $fields->fieldByName('Root.Categorisation') - ->addExtraClass('blog-cms-categorisation') - ->setTitle(_t(__CLASS__ . '.Categorisation', 'Categorisation')); + $fields->addFieldToTab('Root.Categorisation.Categories', $categories); + $fields->addFieldToTab('Root.Categorisation.Tags', $tags); }); return parent::getCMSFields(); @@ -199,6 +242,7 @@ class Blog extends Page implements PermissionProvider Requirements::css('silverstripe/blog:client/dist/styles/main.css'); Requirements::javascript('silverstripe/blog:client/dist/js/main.bundle.js'); } + /** * {@inheritdoc} */ @@ -249,7 +293,7 @@ class Blog extends Page implements PermissionProvider /** * Determine if the given member belongs to the given relation. * - * @param Member $member + * @param Member $member * @param DataList $relation * * @return bool @@ -527,7 +571,7 @@ class Blog extends Page implements PermissionProvider /** * Returns BlogPosts for a given date period. * - * @param int $year + * @param int $year * @param null|int $month * @param null|int $day * @@ -592,13 +636,7 @@ class Blog extends Page implements PermissionProvider */ public function ProfileLink($urlSegment) { - $baseLink = $this->Link(); - if ($baseLink === '/') { - // Handle homepage blogs - $baseLink = RootURLController::get_homepage_link(); - } - - return Controller::join_links($baseLink, 'profile', $urlSegment); + return Controller::join_links($this->Link('Profile'), $urlSegment); } /** @@ -628,22 +666,22 @@ class Blog extends Page implements PermissionProvider { return [ Blog::MANAGE_USERS => [ - 'name' => _t( + 'name' => _t( __CLASS__ . '.PERMISSION_MANAGE_USERS_DESCRIPTION', 'Manage users for individual blogs' ), - 'help' => _t( + 'help' => _t( __CLASS__ . '.PERMISSION_MANAGE_USERS_HELP', 'Allow assignment of Editors, Writers, or Contributors to blogs' ), 'category' => _t(__CLASS__ . '.PERMISSIONS_CATEGORY', 'Blog permissions'), - 'sort' => 100 + 'sort' => 100 ] ]; } /** - * {@inheritdoc} + * @throws ValidationException */ protected function onBeforeWrite() { @@ -653,6 +691,9 @@ class Blog extends Page implements PermissionProvider /** * Assign users as necessary to the blog group. + * + * @throws ValidationException + * @throws Exception */ protected function assignGroup() { @@ -665,6 +706,7 @@ class Blog extends Page implements PermissionProvider // Must check if the method exists or else an error occurs when changing page type if ($this->hasMethod('Editors')) { foreach ([$this->Editors(), $this->Writers(), $this->Contributors()] as $levels) { + /** @var Member $user */ foreach ($levels as $user) { if (!$user->inGroup($group)) { $user->Groups()->add($group); @@ -678,13 +720,14 @@ class Blog extends Page implements PermissionProvider * Gets or creates the group used to assign CMS access. * * @return Group + * @throws ValidationException */ protected function getUserGroup() { $code = $this->config()->get('grant_user_group'); + /** @var Group $group */ $group = Group::get()->filter('Code', $code)->first(); - if ($group) { return $group; } diff --git a/src/Model/BlogCategory.php b/src/Model/BlogCategory.php index ae69c7d..7bbfdf5 100644 --- a/src/Model/BlogCategory.php +++ b/src/Model/BlogCategory.php @@ -3,16 +3,14 @@ namespace SilverStripe\Blog\Model; use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\ManyManyList; /** * A blog category for generalising blog posts. * -* - * @method Blog Blog() - * + * @method ManyManyList|BlogPost[] BlogPosts() * @property string $Title * @property string $URLSegment - * @property int $BlogID */ class BlogCategory extends DataObject implements CategorisationObject { @@ -44,8 +42,8 @@ class BlogCategory extends DataObject implements CategorisationObject /** * @var array */ - private static $has_one = [ - 'Blog' => Blog::class, + private static $indexes = [ + 'URLSegment' => true, ]; /** diff --git a/src/Model/BlogController.php b/src/Model/BlogController.php index 9a8fd83..e27a195 100644 --- a/src/Model/BlogController.php +++ b/src/Model/BlogController.php @@ -6,14 +6,17 @@ use PageController; use SilverStripe\Control\Director; use SilverStripe\Control\HTTPResponse_Exception; use SilverStripe\Control\RSS\RSSFeed; -use SilverStripe\ORM\ArrayList; use SilverStripe\ORM\DataList; use SilverStripe\ORM\FieldType\DBDatetime; +use SilverStripe\ORM\FieldType\DBHTMLText; use SilverStripe\ORM\PaginatedList; +use SilverStripe\ORM\SS_List; use SilverStripe\Security\Member; use SilverStripe\View\Parsers\URLSegmentFilter; -use SilverStripe\Control\HTTPRequest; +/** + * @method Blog data() + */ class BlogController extends PageController { /** @@ -31,17 +34,17 @@ class BlogController extends PageController * @var array */ private static $url_handlers = [ - 'tag/$Tag!/$Rss' => 'tag', - 'category/$Category!/$Rss' => 'category', + 'tag/$Tag!/$Rss' => 'tag', + 'category/$Category!/$Rss' => 'category', 'archive/$Year!/$Month/$Day' => 'archive', - 'profile/$URLSegment!' => 'profile' + 'profile/$Profile!' => 'profile' ]; /** * @var array */ private static $casting = [ - 'MetaTitle' => 'Text', + 'MetaTitle' => 'Text', 'FilterDescription' => 'Text' ]; @@ -60,27 +63,11 @@ class BlogController extends PageController */ protected $blogPosts; - /** - * @return string - */ - public function index(HTTPRequest $request) - { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - - $this->blogPosts = $dataRecord->getBlogPosts(); - - return $this->render(); - } - /** * Renders a Blog Member's profile. * * @throws HTTPResponse_Exception - * - * @return string + * @return $this */ public function profile() { @@ -88,45 +75,59 @@ class BlogController extends PageController $this->httpError(404, 'Not Found'); } - $profile = $this->getCurrentProfile(); - - if (!$profile) { - return $this->httpError(404, 'Not Found'); + // Get profile posts + $posts = $this->getCurrentProfilePosts(); + if (!$posts) { + $this->httpError(404, 'Not Found'); } - $this->blogPosts = $this->getCurrentProfilePosts(); - - return $this->render(); + $this->setFilteredPosts($posts); + return $this; } /** * Get the Member associated with the current URL segment. * - * @return null|Member + * @return null|Member|BlogMemberExtension */ public function getCurrentProfile() { - $urlSegment = $this->request->param('URLSegment'); - if ($urlSegment) { - $filter = URLSegmentFilter::create(); - // url encode unless it's multibyte (already pre-encoded in the database) - // see https://github.com/silverstripe/silverstripe-cms/pull/2384 - if (!$filter->getAllowMultibyte()) { - $urlSegment = rawurlencode($urlSegment); - } - - return Member::get() - ->filter('URLSegment', $urlSegment) - ->first(); + $segment = $this->getCurrentProfileURLSegment(); + if (!$segment) { + return null; } - return null; + /** @var Member $profile */ + $profile = Member::get() + ->find('URLSegment', $segment); + return $profile; + } + + /** + * Get URL Segment of current profile + * + * @return null|string + */ + public function getCurrentProfileURLSegment() + { + $segment = isset($this->urlParams['Profile']) + ? $this->urlParams['Profile'] + : null; + if (!$segment) { + return null; + } + + // url encode unless it's multibyte (already pre-encoded in the database) + // see https://github.com/silverstripe/silverstripe-cms/pull/2384 + return URLSegmentFilter::singleton()->getAllowMultibyte() + ? $segment + : rawurlencode($segment); } /** * Get posts related to the current Member profile. * - * @return null|DataList + * @return null|DataList|BlogPost[] */ public function getCurrentProfilePosts() { @@ -142,166 +143,190 @@ class BlogController extends PageController /** * Renders an archive for a specified date. This can be by year or year/month. * - * @return null|string + * @return $this + * @throws HTTPResponse_Exception */ public function archive() { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - $year = $this->getArchiveYear(); $month = $this->getArchiveMonth(); $day = $this->getArchiveDay(); - if ($this->request->param('Month') && !$month) { + // Validate all values + if ($year === false || $month === false || $day === false) { $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; + $posts = $this->data()->getArchivedBlogPosts($year, $month, $day); + $this->setFilteredPosts($posts); + return $this; } /** * Fetches the archive year from the url. * - * @return int + * Returns int if valid, current year if not provided, false if invalid value + * + * @return int|false */ 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') { + if (isset($this->urlParams['Year']) + && preg_match('/^[0-9]{4}$/', $this->urlParams['Year']) + ) { + return (int)$this->urlParams['Year']; + } + + if ($this->urlParams['Action'] === 'archive') { return DBDatetime::now()->Year(); } - return null; + return false; } /** * Fetches the archive money from the url. * - * @return null|int + * Returns int if valid, null if not provided, false if invalid value + * + * @return null|int|false */ public function getArchiveMonth() { - $month = $this->request->param('Month'); + $month = isset($this->urlParams['Month']) + ? $this->urlParams['Month'] + : null; - if (preg_match('/^[0-9]{1,2}$/', $month)) { - if ($month > 0 && $month < 13) { - if (checkdate($month, 01, $this->getArchiveYear())) { - return (int) $month; - } - } + if (preg_match('/^[0-9]{1,2}$/', $month) + && $month > 0 + && $month < 13 + ) { + return (int)$month; } - return null; + return false; } /** * Fetches the archive day from the url. * - * @return null|int + * Returns int if valid, null if not provided, false if invalid value + * + * @return null|int|false */ public function getArchiveDay() { - $day = $this->request->param('Day'); + $day = isset($this->urlParams['Day']) + ? $this->urlParams['Day'] + : null; - if (preg_match('/^[0-9]{1,2}$/', $day)) { - if (checkdate($this->getArchiveMonth(), $day, $this->getArchiveYear())) { - return (int) $day; - } + // Cannot calculate day without month and year + $month = $this->getArchiveMonth(); + $year = $this->getArchiveYear(); + if (!$month || !$year) { + return false; } - return null; + if (preg_match('/^[0-9]{1,2}$/', $day) && checkdate($month, $day, $year)) { + return (int)$day; + } + + return false; } /** * Renders the blog posts for a given tag. * - * @return null|string + * @return DBHTMLText|$this + * @throws HTTPResponse_Exception */ public function tag() { + // Ensure tag exists $tag = $this->getCurrentTag(); - - if ($tag) { - $this->blogPosts = $tag->BlogPosts(); - - if ($this->isRSS()) { - return $this->rssFeed($this->blogPosts, $tag->getLink()); - } else { - return $this->render(); - } + if (!$tag) { + $this->httpError(404, 'Not Found'); } - $this->httpError(404, 'Not Found'); + // Get posts with this tag + $posts = $this + ->data() + ->getBlogPosts() + ->filter(['Tags.URLSegment' => $tag->URLSegment]); // Soft duplicate handling - return null; + $this->setFilteredPosts($posts); + + // Render as RSS if provided + if ($this->isRSS()) { + return $this->rssFeed($posts, $tag->getLink()); + } + + return $this; } /** - * Tag Getter for use in templates. + * Get BlogTag assigned to current filter * * @return null|BlogTag */ public function getCurrentTag() { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - $tag = $this->request->param('Tag'); - if ($tag) { - $filter = URLSegmentFilter::create(); - // url encode unless it's multibyte (already pre-encoded in the database) - // see https://github.com/silverstripe/silverstripe-cms/pull/2384 - if (!$filter->getAllowMultibyte()) { - $tag = rawurlencode($tag); - } - - return $dataRecord->Tags() - ->filter('URLSegment', $tag) - ->first(); + $segment = $this->getCurrentTagURLSegment(); + if (!$segment) { + return null; } - return null; + + /** @var BlogTag $tag */ + $tag = $this + ->data() + ->Tags(false)// Show "no results" instead of "404" + ->find('URLSegment', $segment); + return $tag; + } + + /** + * Get URLSegment of selected category (not: URLEncoded based on multibyte) + * + * @return string|null + */ + public function getCurrentTagURLSegment() + { + $segment = isset($this->urlParams['Tag']) + ? $this->urlParams['Tag'] + : null; + + // url encode unless it's multibyte (already pre-encoded in the database) + // see https://github.com/silverstripe/silverstripe-cms/pull/2384 + return URLSegmentFilter::singleton()->getAllowMultibyte() + ? $segment + : rawurlencode($segment); } /** * Renders the blog posts for a given category. * - * @return null|string + * @return DBHTMLText|$this + * @throws HTTPResponse_Exception */ public function category() { $category = $this->getCurrentCategory(); - if ($category) { - $this->blogPosts = $category->BlogPosts(); - - if ($this->isRSS()) { - return $this->rssFeed($this->blogPosts, $category->getLink()); - } - return $this->render(); + if (!$category) { + $this->httpError(404, 'Not Found'); } - $this->httpError(404, 'Not Found'); + // Get posts with this category + $posts = $this + ->data() + ->getBlogPosts() + ->filter(['Categories.URLSegment' => $category->URLSegment]); // Soft duplicate handling + $this->setFilteredPosts($posts); - return null; + if ($this->isRSS()) { + return $this->rssFeed($posts, $category->getLink()); + } + return $this; } /** @@ -311,24 +336,35 @@ class BlogController extends PageController */ public function getCurrentCategory() { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - $category = $this->request->param('Category'); - if ($category) { - $filter = URLSegmentFilter::create(); - // url encode unless it's multibyte (already pre-encoded in the database) - // see https://github.com/silverstripe/silverstripe-cms/pull/2384 - if (!$filter->getAllowMultibyte()) { - $category = rawurlencode($category); - } - - return $dataRecord->Categories() - ->filter('URLSegment', $category) - ->first(); + $segment = $this->getCurrentCategoryURLSegment(); + if (!$segment) { + return null; } - return null; + + /** @var BlogCategory $category */ + $category = $this + ->data() + ->Categories(false)// Show "no results" instead of "404" + ->find('URLSegment', $segment); + return $category; + } + + /** + * Get URLSegment of selected category + * + * @return string|null + */ + public function getCurrentCategoryURLSegment() + { + $segment = isset($this->urlParams['Category']) + ? $this->urlParams['Category'] + : null; + + // url encode unless it's multibyte (already pre-encoded in the database) + // see https://github.com/silverstripe/silverstripe-cms/pull/2384 + return URLSegmentFilter::singleton()->getAllowMultibyte() + ? $segment + : rawurlencode($segment); } /** @@ -406,13 +442,13 @@ class BlogController extends PageController ); } - if ($this->owner->getArchiveYear()) { - if ($this->owner->getArchiveDay()) { - $date = $this->owner->getArchiveDate()->Nice(); - } elseif ($this->owner->getArchiveMonth()) { - $date = $this->owner->getArchiveDate()->format('MMMM, y'); + if ($this->getArchiveYear()) { + if ($this->getArchiveDay()) { + $date = $this->getArchiveDate()->Nice(); + } elseif ($this->getArchiveMonth()) { + $date = $this->getArchiveDate()->format('MMMM, y'); } else { - $date = $this->owner->getArchiveDate()->format('y'); + $date = $this->getArchiveDate()->format('y'); } $items[] = _t( @@ -436,6 +472,28 @@ class BlogController extends PageController return $result; } + /** + * Get filtered blog posts + * + * @return DataList|BlogPost[] + */ + public function getFilteredPosts() + { + return $this->blogPosts ?: $this->data()->getBlogPosts(); + } + + /** + * Set filtered posts + * + * @param SS_List|BlogPost[] $posts + * @return $this + */ + public function setFilteredPosts($posts) + { + $this->blogPosts = $posts; + return $this; + } + /** * Returns a list of paginated blog posts based on the BlogPost dataList. * @@ -443,12 +501,12 @@ class BlogController extends PageController */ public function PaginatedList() { - $allPosts = $this->blogPosts ?: ArrayList::create(); + $allPosts = $this->getFilteredPosts(); $posts = PaginatedList::create($allPosts); // Set appropriate page size - if ($this->PostsPerPage > 0) { - $pageSize = $this->PostsPerPage; + if ($this->data()->PostsPerPage > 0) { + $pageSize = $this->data()->PostsPerPage; } elseif ($count = $allPosts->count()) { $pageSize = $count; } else { @@ -482,7 +540,7 @@ class BlogController extends PageController return null; } - /** + /** * Returns the absolute link to the previous page for use in the page meta tags. This helps search engines * find the pagination and index all pages properly. * @@ -507,14 +565,7 @@ class BlogController extends PageController */ public function rss() { - /** - * @var Blog $dataRecord - */ - $dataRecord = $this->dataRecord; - - $this->blogPosts = $dataRecord->getBlogPosts(); - - return $this->rssFeed($this->blogPosts, $this->Link()); + return $this->rssFeed($this->getFilteredPosts(), $this->Link()); } /** @@ -561,13 +612,18 @@ class BlogController extends PageController * Displays an RSS feed of the given blog posts. * * @param DataList $blogPosts - * @param string $link + * @param string $link * - * @return string + * @return DBHTMLText */ protected function rssFeed($blogPosts, $link) { - $rss = RSSFeed::create($blogPosts, $link, $this->MetaTitle, $this->MetaDescription); + $rss = RSSFeed::create( + $blogPosts, + $link, + $this->getMetaTitle(), + $this->data()->MetaDescription + ); $this->extend('updateRss', $rss); @@ -581,7 +637,6 @@ class BlogController extends PageController */ protected function isRSS() { - $rss = $this->request->param('Rss'); - return (is_string($rss) && strcasecmp($rss, 'rss') == 0); + return isset($this->urlParams['RSS']) && strcasecmp($this->urlParams['RSS'], 'rss') == 0; } } diff --git a/src/Model/BlogMemberExtension.php b/src/Model/BlogMemberExtension.php index 86f6cec..ee7e553 100644 --- a/src/Model/BlogMemberExtension.php +++ b/src/Model/BlogMemberExtension.php @@ -10,6 +10,7 @@ use SilverStripe\Forms\GridField\GridFieldAddNewButton; use SilverStripe\Forms\Tab; use SilverStripe\Forms\TextareaField; use SilverStripe\ORM\DataExtension; +use SilverStripe\ORM\ManyManyList; use SilverStripe\Security\Member; use SilverStripe\View\Parsers\URLSegmentFilter; use SilverStripe\View\Requirements; @@ -17,6 +18,10 @@ use SilverStripe\View\Requirements; /** * This class is responsible for add Blog specific behaviour to Members. * + * @property string $URLSegment + * @property string $BlogProfileSummary + * @method Image BlogProfileImage() + * @method ManyManyList|BlogPost[] BlogPosts() */ class BlogMemberExtension extends DataExtension { diff --git a/src/Model/BlogObject.php b/src/Model/BlogObject.php index ba83d9a..292efec 100644 --- a/src/Model/BlogObject.php +++ b/src/Model/BlogObject.php @@ -8,6 +8,7 @@ use SilverStripe\Forms\Tab; use SilverStripe\Forms\TabSet; use SilverStripe\Forms\TextField; use SilverStripe\ORM\DataList; +use SilverStripe\ORM\ManyManyList; use SilverStripe\ORM\ValidationResult; use SilverStripe\Security\Member; use SilverStripe\Security\Permission; @@ -20,7 +21,7 @@ use SilverStripe\View\Parsers\URLSegmentFilter; trait BlogObject { /** - * @return DataList + * @return ManyManyList|BlogPost[] */ public function BlogPosts() { @@ -31,6 +32,22 @@ trait BlogObject return $blogPosts; } + /** + * Get blog this tag was queried from + * + * @return Blog|null + */ + public function Blog() + { + $blogID = $this->getSourceQueryParam('BlogID'); + if ($blogID) { + /** @var Blog $blog */ + $blog = Blog::get()->byID($blogID); + return $blog; + } + return null; + } + /** * {@inheritdoc} */ @@ -75,14 +92,18 @@ trait BlogObject } /** - * Returns a relative link to this category. + * Returns a relative link to this category or tag * * @return string */ public function getLink() { + $blog = $this->Blog(); + if (!$blog) { + return null; + } return Controller::join_links( - $this->Blog()->Link(), + $blog->Link(), $this->getListUrlSegment(), $this->URLSegment ); @@ -158,14 +179,12 @@ trait BlogObject return $this->Blog()->canEdit($member); } - /** - * {@inheritdoc} - */ protected function onBeforeWrite() { parent::onBeforeWrite(); + if ($this->exists() || empty($this->URLSegment)) { - return $this->generateURLSegment(); + $this->generateURLSegment(); } } @@ -178,7 +197,7 @@ trait BlogObject */ public function generateURLSegment($increment = 0) { - $increment = (int) $increment; + $increment = (int)$increment; $filter = URLSegmentFilter::create(); // Setting this to on. Because of the UI flow, it would be quite a lot of work @@ -209,12 +228,7 @@ trait BlogObject protected function getDuplicatesByField($field) { $duplicates = DataList::create(self::class) - ->filter( - [ - $field => $this->$field, - 'BlogID' => (int) $this->BlogID - ] - ); + ->filter([$field => $this->$field]); if ($this->ID) { $duplicates = $duplicates->exclude('ID', $this->ID); diff --git a/src/Model/BlogPost.php b/src/Model/BlogPost.php index 321f6a6..d03b0d5 100644 --- a/src/Model/BlogPost.php +++ b/src/Model/BlogPost.php @@ -313,14 +313,6 @@ class BlogPost extends Page } // Get categories and tags - $parent = $this->Parent(); - $categories = $parent instanceof Blog - ? $parent->Categories() - : BlogCategory::get(); - $tags = $parent instanceof Blog - ? $parent->Tags() - : BlogTag::get(); - // @todo: Reimplement the sidebar // $options = BlogAdminSidebar::create( $fields->addFieldsToTab( @@ -330,7 +322,7 @@ class BlogPost extends Page TagField::create( 'Categories', _t(__CLASS__ . '.Categories', 'Categories'), - $categories, + BlogCategory::get(), $this->Categories() ) ->setCanCreate($this->canCreateCategories()) @@ -338,7 +330,7 @@ class BlogPost extends Page TagField::create( 'Tags', _t(__CLASS__ . '.Tags', 'Tags'), - $tags, + BlogTag::get(), $this->Tags() ) ->setCanCreate($this->canCreateTags()) @@ -816,4 +808,16 @@ class BlogPost extends Page $this->Authors()->add($member); } } + + /** + * So that tags / categories queried through this post generate the correct Link() + * + * @return array + */ + public function getInheritableQueryParams() + { + $params = parent::getInheritableQueryParams(); + $params['BlogID'] = $this->ParentID; + return $params; + } } diff --git a/src/Model/BlogTag.php b/src/Model/BlogTag.php index 4180f67..2399880 100644 --- a/src/Model/BlogTag.php +++ b/src/Model/BlogTag.php @@ -3,6 +3,7 @@ namespace SilverStripe\Blog\Model; use SilverStripe\ORM\DataObject; +use SilverStripe\ORM\ManyManyList; /** * A blog tag for keyword descriptions of a blog post. @@ -10,9 +11,9 @@ use SilverStripe\ORM\DataObject; * * @method Blog Blog() * + * @method ManyManyList|BlogPost[] BlogPosts() * @property string $Title * @property string $URLSegment - * @property int $BlogID */ class BlogTag extends DataObject implements CategorisationObject { @@ -44,8 +45,8 @@ class BlogTag extends DataObject implements CategorisationObject /** * @var array */ - private static $has_one = [ - 'Blog' => Blog::class + private static $indexes = [ + 'URLSegment' => true, ]; /** diff --git a/src/Model/CategorisationObject.php b/src/Model/CategorisationObject.php index 827035b..3e29247 100644 --- a/src/Model/CategorisationObject.php +++ b/src/Model/CategorisationObject.php @@ -2,8 +2,10 @@ namespace SilverStripe\Blog\Model; +use SilverStripe\ORM\ManyManyList; + /** - * @method ManyManyList BlogPosts + * @method ManyManyList|BlogPost[] BlogPosts() */ interface CategorisationObject {