Merge pull request #421 from robbieaverill/ss4

SilverStripe 4 compatibility
This commit is contained in:
Daniel Hensby 2017-01-26 12:29:33 +00:00 committed by GitHub
commit d5a8761acf
81 changed files with 2553 additions and 2508 deletions

3
.gitignore vendored
View File

@ -1,5 +1,6 @@
.ssh/*
.bash*
.profile
vendor/*
/vendor/*
.sass-cache/*
/node_modules/

View File

@ -6,4 +6,4 @@ checks:
duplication: true
filter:
paths: [code/*, tests/*]
paths: [src/*, tests/*]

View File

@ -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"

33
.upgrade.yml Normal file
View File

@ -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

View File

@ -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.

View File

@ -1,8 +1,8 @@
<?php
/**
* Fetches the name of the current module folder name.
*
* @return string
**/
define('BLOGGER_DIR', ltrim(Director::makeRelative(realpath(__DIR__)), DIRECTORY_SEPARATOR));
<?php
/**
* Fetches the name of the current module folder name.
*
* @return string
**/
define('BLOGGER_DIR', basename(dirname(__FILE__)));

View File

@ -3,15 +3,15 @@ Name: blogcommentsconfig
Only:
moduleexists: comments
---
Comment:
SilverStripe\Comments\Model\Comment:
extensions:
- BlogCommentExtension
- SilverStripe\Blog\Model\BlogCommentExtension
---
Name: blogcommentnotifications
Only:
moduleexists: 'comment-notifications'
---
BlogPost:
SilverStripe\Blog\Model\BlogPost:
extensions:
- BlogPostNotifications
- SilverStripe\Blog\Model\BlogPostNotifications

View File

@ -1,6 +1,6 @@
---
Name: blogconfig
---
Member:
SilverStripe\Security\Member:
extensions:
- BlogMemberExtension
- SilverStripe\Blog\Model\BlogMemberExtension

9
_config/legacy.yml Normal file
View File

@ -0,0 +1,9 @@
---
Name: bloglegacy
---
SilverStripe\ORM\DatabaseAdmin:
classname_value_remapping:
Blog: SilverStripe\Blog\Model\Blog
BlogCategory: SilverStripe\Blog\Model\BlogCategory
BlogPost: SilverStripe\Blog\Model\BlogPost
BlogTag: SilverStripe\Blog\Model\BlogTag

View File

@ -1,57 +0,0 @@
<?php
class GridFieldCategorisationConfig extends GridFieldConfig_RecordEditor
{
/**
* @param int $itemsPerPage
* @param array|SS_List $mergeRecords
* @param string $parentType
* @param string $parentMethod
* @param string $childMethod
*/
public function __construct($itemsPerPage = 15, $mergeRecords, $parentType, $parentMethod, $childMethod)
{
parent::__construct($itemsPerPage);
$this->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',
));
}
}

View File

@ -1,112 +0,0 @@
<?php
/**
* @deprecated since version 2.0
*
* @property int $ParentID
* @property string $Date
* @property string $PublishDate
* @property string $Tags
*/
class BlogEntry extends BlogPost implements MigratableObject
{
/**
* @var string
*/
private static $hide_ancestor = 'BlogEntry';
/**
* @var array
*/
private static $db = array(
'Date' => '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
{
}

View File

@ -1,77 +0,0 @@
<?php
/**
* @deprecated since version 2.0
*/
class BlogHolder extends BlogTree implements MigratableObject
{
/**
* @var string
*/
private static $hide_ancestor = 'BlogHolder';
/**
* @var array
*/
private static $db = array(
'AllowCustomAuthors' => '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
{
}

View File

@ -1,56 +0,0 @@
<?php
/**
* @deprecated since version 2.0
*/
class BlogTree extends Page implements MigratableObject
{
/**
* @var string
*/
private static $hide_ancestor = 'BlogTree';
/**
* @var array
*/
private static $db = array(
'Name' => '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
{
}

View File

@ -1,92 +0,0 @@
<?php
class BlogMigrationTask extends MigrationTask
{
/**
* Should this task be invoked automatically via dev/build?
*
* @config
*
* @var bool
*/
private static $run_during_dev_build = true;
/**
* {@inheritdoc}
*/
public function up()
{
$classes = ClassInfo::implementorsOf('MigratableObject');
$this->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 . "<br/>";
}
}
/**
* 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');
}
}

View File

@ -1,9 +0,0 @@
<?php
interface MigratableObject
{
/**
* Migrate the object up to the current version.
*/
public function up();
}

View File

@ -1,54 +0,0 @@
<?php
if (!class_exists('Widget')) {
return;
}
/**
* @deprecated since version 2.0
*
* @property string $DisplayMode
* @property string $ArchiveType
*/
class ArchiveWidget extends BlogArchiveWidget implements MigratableObject
{
/**
* @var array
*/
private static $db = array(
'DisplayMode' => '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";
}
}

View File

@ -1,47 +0,0 @@
<?php
if (!class_exists('Widget')) {
return;
}
/**
* A list of tags associated with blog posts.
*
* @package blog
*/
class TagCloudWidget extends BlogTagsWidget implements MigratableObject
{
/**
* @var array
*/
private static $db = array(
'Title' => '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";
}
}

View File

@ -1,45 +0,0 @@
<?php
/**
* This is responsible for filtering only published posts to users who do not have permission to
* view non-published posts.
*
* @package silverstripe
* @subpackage blog
*/
class BlogPostFilter extends DataExtension
{
/**
* Augment queries so that we don't fetch unpublished articles.
*
* @param SQLQuery $query
*/
public function augmentSQL(SQLQuery &$query)
{
$stage = Versioned::current_stage();
if (Controller::curr() instanceof LeftAndMain) {
return;
}
if ($stage == 'Live' || !Permission::check('VIEW_DRAFT_CONTENT')) {
$query->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"');
}
}

View File

@ -1,78 +0,0 @@
<?php
/**
* Adds URLSegment functionality to Tags & Categories.
*
* @package silverstripe
* @subpackage blog
*/
class URLSegmentExtension extends DataExtension
{
/**
* @var array
*/
private static $db = array(
'URLSegment' => '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;
}
}

View File

@ -1,188 +0,0 @@
<?php
/**
* A blog category for generalising blog posts.
*
* @package silverstripe
* @subpackage blog
*
* @method Blog Blog()
*
* @property string $URLSegment
* @property int $BlogID
*/
class BlogCategory extends DataObject implements CategorisationObject
{
/**
* Use an exception code so that attempted writes can continue on
* duplicate errors.
*
* @const string
* This must be a string because ValidationException has decided we can't use int
*/
const DUPLICATE_EXCEPTION = "DUPLICATE";
/**
* @var array
*/
private static $db = array(
'Title' => '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);
}
}

View File

@ -1,189 +0,0 @@
<?php
/**
* A blog tag for keyword descriptions of a blog post.
*
* @package silverstripe
* @subpackage blog
*
* @method Blog Blog()
*
* @property string $Title
* @property string $URLSegment
* @property int $BlogID
*/
class BlogTag extends DataObject implements CategorisationObject
{
/**
* Use an exception code so that attempted writes can continue on
* duplicate errors.
*
* @const string
* This must be a string because ValidationException has decided we can't use int
*/
const DUPLICATE_EXCEPTION = "DUPLICATE";
/**
* @var array
*/
private static $db = array(
'Title' => '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);
}
}

View File

@ -1,100 +0,0 @@
<?php
if (!class_exists("Widget")) {
return;
}
/**
* @method Blog Blog()
*/
class BlogCategoriesWidget extends Widget
{
/**
* @var string
*/
private static $title = 'Categories';
/**
* @var string
*/
private static $cmsTitle = 'Blog Categories';
/**
* @var string
*/
private static $description = 'Displays a list of blog categories.';
/**
* @var array
*/
private static $db = array(
'Limit' => '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
{
}

View File

@ -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
}

View File

@ -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

View File

@ -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; }

View File

@ -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; }

17
gulpfile.js Normal file
View File

@ -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
});

View File

@ -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()
});
}
});

View File

@ -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);

View File

@ -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

28
package.json Normal file
View File

@ -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"
}
}

18
phpunit.xml.dist Normal file
View File

@ -0,0 +1,18 @@
<phpunit bootstrap="framework/tests/bootstrap.php" colors="true">
<testsuite name="blog">
<directory>blog/tests</directory>
</testsuite>
<listeners>
<listener class="SilverStripe\Dev\TestListener" />
</listeners>
<filter>
<whitelist addUncoveredFilesFromWhitelist="true">
<directory suffix=".php">blog/src/</directory>
<exclude>
<directory suffix=".php">blog/tests/</directory>
</exclude>
</whitelist>
</filter>
</phpunit>

View File

@ -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;
}
}

View File

@ -0,0 +1,68 @@
<?php
namespace SilverStripe\Blog\Admin;
use SilverStripe\Blog\Forms\GridField\GridFieldAddByDBField;
use SilverStripe\Blog\Admin\GridFieldMergeAction;
use SilverStripe\Blog\Model\CategorisationObject;
use SilverStripe\Forms\GridField\GridFieldConfig_RecordEditor;
class GridFieldCategorisationConfig extends GridFieldConfig_RecordEditor
{
/**
* @param int $itemsPerPage
* @param array|SS_List $mergeRecords
* @param string $parentType
* @param string $parentMethod
* @param string $childMethod
*/
public function __construct($itemsPerPage = 15, $mergeRecords, $parentType, $parentMethod, $childMethod)
{
parent::__construct($itemsPerPage);
$this->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'
)
);
}
}

View File

@ -1,5 +1,9 @@
<?php
namespace SilverStripe\Blog\Admin;
use SilverStripe\Forms\GridField\GridField_FormAction;
class GridFieldFormAction extends GridField_FormAction
{
/**

View File

@ -1,5 +1,17 @@
<?php
namespace SilverStripe\Blog\Admin;
use SilverStripe\Blog\Admin\GridFieldFormAction;
use SilverStripe\Control\Controller;
use Silverstripe\Forms\DropdownField;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridField_ActionProvider;
use SilverStripe\Forms\GridField\GridField_ColumnProvider;
/**
* @package blog
*/
class GridFieldMergeAction implements GridField_ColumnProvider, GridField_ActionProvider
{
/**
@ -70,11 +82,11 @@ class GridFieldMergeAction implements GridField_ColumnProvider, GridField_Action
public function getColumnContent($gridField, $record, $columnName)
{
if ($columnName === 'MergeAction' && $record->{$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',

View File

@ -1,5 +1,10 @@
<?php
namespace SilverStripe\Blog\Forms;
use SilverStripe\Control\Cookie;
use SilverStripe\Forms\FieldGroup;
class BlogAdminSidebar extends FieldGroup
{
/**

View File

@ -1,5 +1,20 @@
<?php
namespace SilverStripe\Blog\Forms\GridField;
use UnexpectedValueException;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\GridField\GridField_ActionProvider;
use SilverStripe\Forms\GridField\GridField_FormAction;
use SilverStripe\Forms\GridField\GridField_HTMLProvider;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\Security\Security;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;
/**
* Adds a component which allows a user to add a new DataObject by database field.
*
@ -79,7 +94,7 @@ class GridFieldAddByDBField implements GridField_ActionProvider, GridField_HTMLP
if ($obj->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));
}
}

View File

@ -1,5 +1,12 @@
<?php
namespace SilverStripe\Blog\Forms\GridField;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Lumberjack\Forms\GridFieldSiteTreeState;
use SilverStripe\View\Requirements;
/**
* Provides a component to the {@link GridField} which tells the user whether or not a blog post
* has been published and when.

View File

@ -1,5 +1,10 @@
<?php
namespace SilverStripe\Blog\Forms\GridField;
use SilverStripe\Lumberjack\Forms\GridFieldConfig_Lumberjack;
use SilverStripe\Lumberjack\Forms\GridFieldSiteTreeState;
/**
* GridField config necessary for managing a SiteTree object.
*
@ -15,7 +20,7 @@ class GridFieldConfig_BlogPost extends GridFieldConfig_Lumberjack
{
parent::__construct($itemsPerPage);
$this->removeComponentsByType('GridFieldSiteTreeState');
$this->removeComponentsByType(GridFieldSiteTreeState::class);
$this->addComponent(new GridFieldBlogPostState());
}
}

View File

@ -1,5 +1,34 @@
<?php
namespace SilverStripe\Blog\Model;
use Page;
use PageController;
use SilverStripe\Blog\Admin\GridFieldCategorisationConfig;
use SilverStripe\Blog\Forms\GridField\GridFieldConfig_BlogPost;
use SilverStripe\Blog\Model\BlogCategory;
use SilverStripe\Blog\Model\BlogFilter;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Blog\Model\BlogTag;
use SilverStripe\CMS\Controllers\RootURLController;
use SilverStripe\Control\Controller;
use SilverStripe\Control\RSS\RSSFeed;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\ListboxField;
use SilverStripe\Forms\NumericField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\PaginatedList;
use SilverStripe\ORM\UnsavedRelationList;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\Security\PermissionProvider;
use SilverStripe\View\Requirements;
/**
* Blog Holder
*
@ -50,6 +79,12 @@ class Blog extends Page implements PermissionProvider
*/
private static $grant_user_group = 'blog-users';
/**
* {@inheritDoc}
* @var string
*/
private static $table_name = 'Blog';
/**
* @var array
*/
@ -61,31 +96,31 @@ class Blog extends Page implements PermissionProvider
* @var array
*/
private static $has_many = array(
'Tags' => '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('<a class="toggle-description">help</a>')
->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.<br />
@ -327,7 +376,7 @@ class Blog extends Page implements PermissionProvider
}
$writerField = ListboxField::create('Writers', 'Writers', $members)
->setMultiple(true)
// ->setMultiple(true)
->setRightTitle('<a class="toggle-description">help</a>')
->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.<br />
@ -343,7 +392,7 @@ class Blog extends Page implements PermissionProvider
}
$contributorField = ListboxField::create('Contributors', 'Contributors', $members)
->setMultiple(true)
// ->setMultiple(true)
->setRightTitle('<a class="toggle-description">help</a>')
->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.<br />
@ -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;
}
}
}

View File

@ -0,0 +1,79 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogObject;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Blog\Model\CategorisationObject;
use SilverStripe\ORM\DataObject;
/**
* A blog category for generalising blog posts.
*
* @package silverstripe
* @subpackage blog
*
* @method Blog Blog()
*
* @property string $Title
* @property string $URLSegment
* @property int $BlogID
*/
class BlogCategory extends DataObject implements CategorisationObject
{
use BlogObject;
/**
* Use an exception code so that attempted writes can continue on
* duplicate errors.
*
* @const string
* This must be a string because ValidationException has decided we can't use int
*/
const DUPLICATE_EXCEPTION = 'DUPLICATE';
/**
* {@inheritDoc}
* @var string
*/
private static $table_name = 'BlogCategory';
/**
* @var array
*/
private static $db = array(
'Title' => '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.');
}
}

View File

@ -1,5 +1,10 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\ORM\DataExtension;
/**
* Adds Blog specific behaviour to Comment.
*/

View File

@ -0,0 +1,516 @@
<?php
namespace SilverStripe\Blog\Model;
use PageController;
use SilverStripe\Control\RSS\RSSFeed;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\PaginatedList;
use SilverStripe\Security\Member;
/**
* @package silverstripe
* @subpackage blog
*/
class BlogController extends PageController
{
/**
* @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 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);
}
}

View File

@ -1,5 +1,20 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogFilter\BlogFilterGridField;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Core\ClassInfo;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Tab;
use SilverStripe\Lumberjack\Model\Lumberjack;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\Security\Permission;
/**
* This class is responsible for filtering the SiteTree when necessary and also overlaps into
* filtering only published posts.
@ -17,7 +32,7 @@ class BlogFilter extends Lumberjack
$staged = parent::stageChildren($showAll);
if (!$this->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;
}
}

View File

@ -0,0 +1,22 @@
<?php
namespace SilverStripe\Blog\Model\BlogFilter;
use SilverStripe\Forms\FormTransformation;
use SilverStripe\Forms\GridField\GridField;
/**
* Enables children of non-editable pages to be edited.
*/
class BlogFilterGridField extends GridField
{
/**
* @param FormTransformation $transformation
*
* @return $this
*/
public function transform(FormTransformation $transformation)
{
return $this;
}
}

View File

@ -1,5 +1,18 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Forms\GridField\GridFieldConfig_BlogPost;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\GridField\GridField;
use SilverStripe\Forms\Tab;
use SilverStripe\ORM\DataExtension;
use SilverStripe\Security\Member;
use SilverStripe\View\Parsers\URLSegmentFilter;
use SilverStripe\View\Requirements;
/**
* This class is responsible for add Blog specific behaviour to Members.
*
@ -12,22 +25,22 @@ class BlogMemberExtension extends DataExtension
* @var array
*/
private static $db = array(
'URLSegment' => '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);

246
src/Model/BlogObject.php Normal file
View File

@ -0,0 +1,246 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Control\Controller;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\Tab;
use SilverStripe\Forms\TabSet;
use SilverStripe\Forms\TextField;
use SilverStripe\ORM\DataList;
use SilverStripe\Security\Permission;
use SilverStripe\View\Parsers\URLSegmentFilter;
/**
* An object shared by BlogTag and BlogCategory.
*
* @package silverstripe
* @subpackage blog
*/
trait BlogObject
{
/**
* @return DataList
*/
public function BlogPosts()
{
$blogPosts = parent::BlogPosts();
$this->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();
}

View File

@ -1,5 +1,31 @@
<?php
namespace SilverStripe\Blog\Model;
use Page;
use SilverStripe\Assets\Image;
use SilverStripe\Blog\Forms\BlogAdminSidebar;
use SilverStripe\Blog\Model\BlogCategory;
use SilverStripe\Blog\Model\BlogPostFilter;
use SilverStripe\Blog\Model\BlogTag;
use SilverStripe\Control\Controller;
use SilverStripe\Forms\DatetimeField;
use SilverStripe\Forms\HiddenField;
use SilverStripe\Forms\HTMLEditor\HTMLEditorField;
use SilverStripe\Forms\ListboxField;
use SilverStripe\Forms\TextField;
use SilverStripe\Forms\ToggleCompositeField;
use SilverStripe\Forms\UploadField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\UnsavedRelationList;
use SilverStripe\Security\Group;
use SilverStripe\Security\Member;
use SilverStripe\Security\Permission;
use SilverStripe\TagField\TagField;
use SilverStripe\View\ArrayData;
use SilverStripe\View\Requirements;
/**
* An individual blog post.
*
@ -24,59 +50,65 @@ class BlogPost extends Page
*/
private static $restrict_authors_to_group = false;
/**
* {@inheritDoc}
* @var string
*/
private static $table_name = 'BlogPost';
/**
* @var array
*/
private static $db = array(
'PublishDate' => '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
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Blog\Model;
use PageController;
/**
* @package silverstripe
* @subpackage blog
*/
class BlogPostController extends PageController
{
}

View File

@ -0,0 +1,66 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\Admin\LeftAndMain;
use SilverStripe\Control\Controller;
use SilverStripe\Core\Convert;
use SilverStripe\ORM\DataExtension;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DataQuery;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\Queries\SQLSelect;
use SilverStripe\ORM\Versioning\Versioned;
use SilverStripe\Security\Permission;
/**
* This is responsible for filtering only published posts to users who do not have permission to
* view non-published posts.
*
* @package silverstripe
* @subpackage blog
*/
class BlogPostFilter extends DataExtension
{
/**
* Augment queries so that we don't fetch unpublished articles.
*
* @param SQLQuery $query
*/
public function augmentSQL(SQLSelect $query, DataQuery $dataQuery = null)
{
$stage = Versioned::get_stage();
if (Controller::curr() instanceof LeftAndMain) {
return;
}
if ($stage == 'Live' || !Permission::check('VIEW_DRAFT_CONTENT')) {
$query->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"'
);
}
}

View File

@ -1,5 +1,9 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\ORM\DataExtension;
/**
* Customise blog post to support comment notifications.
*

79
src/Model/BlogTag.php Normal file
View File

@ -0,0 +1,79 @@
<?php
namespace SilverStripe\Blog\Model;
use SilverStripe\ORM\DataObject;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogObject;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Blog\Model\CategorisationObject;
/**
* A blog tag for keyword descriptions of a blog post.
*
* @package silverstripe
* @subpackage blog
*
* @method Blog Blog()
*
* @property string $Title
* @property string $URLSegment
* @property int $BlogID
*/
class BlogTag extends DataObject implements CategorisationObject
{
use BlogObject;
/**
* Use an exception code so that attempted writes can continue on
* duplicate errors.
*
* @const string
* This must be a string because ValidationException has decided we can't use int
*/
const DUPLICATE_EXCEPTION = 'DUPLICATE';
/**
* {@inheritDoc}
* @var string
*/
private static $table_name = 'BlogTag';
/**
* @var array
*/
private static $db = array(
'Title' => '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.');
}
}

View File

@ -1,8 +1,11 @@
<?php
namespace SilverStripe\Blog\Model;
/**
* @method ManyManyList BlogPosts
*/
interface CategorisationObject
{
}

View File

@ -1,9 +1,17 @@
<?php
if (!class_exists('Widget')) {
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Control\Controller;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\NumericField;
use SilverStripe\Widgets\Model\Widget;
/**
* @method Blog Blog()
*
@ -46,7 +54,7 @@ class BlogArchiveWidget extends Widget
* @var array
*/
private static $has_one = array(
'Blog' => '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
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Widgets\Controllers\WidgetController;
class BlogArchiveWidgetController extends WidgetController
{
}

View File

@ -0,0 +1,127 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\NumericField;
use SilverStripe\Widgets\Model\Widget;
/**
* @method Blog Blog()
*/
class BlogCategoriesWidget extends Widget
{
/**
* @var string
*/
private static $title = 'Categories';
/**
* @var string
*/
private static $cmsTitle = 'Blog Categories';
/**
* @var string
*/
private static $description = 'Displays a list of blog categories.';
/**
* @var array
*/
private static $db = array(
'Limit' => '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;
}
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Widgets\Controllers\WidgetController;
class BlogCategoriesWidgetController extends WidgetController
{
}

View File

@ -1,9 +1,16 @@
<?php
if (!class_exists("Widget")) {
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\NumericField;
use SilverStripe\Widgets\Model\Widget;
/**
* @method Blog Blog()
*
@ -37,7 +44,7 @@ class BlogRecentPostsWidget extends Widget
* @var array
*/
private static $has_one = array(
'Blog' => 'Blog',
'Blog' => Blog::class,
);
/**
@ -74,7 +81,3 @@ class BlogRecentPostsWidget extends Widget
return array();
}
}
class BlogRecentPostsWidget_Controller extends Widget_Controller
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Widgets\Controllers\WidgetController;
class BlogRecentPostsWidgetController extends WidgetController
{
}

View File

@ -1,9 +1,19 @@
<?php
if (!class_exists('Widget')) {
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\DropdownField;
use SilverStripe\ORM\ArrayList;
use SilverStripe\ORM\DataObject;
use SilverStripe\ORM\DB;
use SilverStripe\Widgets\Model\Widget;
/**
* @method Blog Blog()
*/
@ -33,7 +43,7 @@ class BlogTagsCloudWidget extends Widget
* @var array
*/
private static $has_one = array(
'Blog' => '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
{
}

View File

@ -0,0 +1,13 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Widgets\Controllers\WidgetController;
class BlogTagsCloudWidgetController extends WidgetController
{
}

View File

@ -1,9 +1,18 @@
<?php
if (!class_exists("Widget")) {
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Core\Convert;
use SilverStripe\Forms\DropdownField;
use SilverStripe\Forms\FieldList;
use SilverStripe\Forms\NumericField;
use SilverStripe\Widgets\Model\Widget;
/**
* @method Blog Blog()
*/
@ -37,7 +46,7 @@ class BlogTagsWidget extends Widget
* @var array
*/
private static $has_one = array(
'Blog' => '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
{
}

View File

@ -0,0 +1,14 @@
<?php
namespace SilverStripe\Blog\Widgets;
if (!class_exists('\\SilverStripe\\Widgets\\Model\\Widget')) {
return;
}
use SilverStripe\Widgets\Controllers\WidgetController;
class BlogTagsWidgetController extends WidgetController
{
}

View File

@ -1,31 +1,39 @@
<p class="blog-post-meta">
<% if $Categories.exists %>
<%t Blog.PostedIn "Posted in" %>
<% loop $Categories %>
<a href="$Link" title="$Title">$Title</a><% if not Last %>, <% else %>;<% end_if %>
<% end_loop %>
<% end_if %>
<% if $Tags.exists %>
<%t Blog.Tagged "Tagged" %>
<% loop $Tags %>
<a href="$Link" title="$Title">$Title</a><% if not Last %>, <% else %>;<% end_if %>
<% end_loop %>
<% end_if %>
<% if $Comments.exists %>
<a href="{$Link}#comments-holder">
<%t Blog.Comments "Comments" %>
$Comments.count
</a>;
<% end_if %>
<%t Blog.Posted "Posted" %>
<a href="$MonthlyArchiveLink">$PublishDate.ago</a>
<% 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 %><a href="$URL">$Name.XML</a><% else %>$Name.XML<% end_if %><% end_loop %>
<% end_if %>
</p>
<p class="blog-post-meta">
<% if $Categories.exists %>
<%t Blog.PostedIn "Posted in" %>
<% loop $Categories %>
<a href="$Link" title="$Title">$Title</a><% if not Last %>, <% else %>;<% end_if %>
<% end_loop %>
<% end_if %>
<% if $Tags.exists %>
<%t Blog.Tagged "Tagged" %>
<% loop $Tags %>
<a href="$Link" title="$Title">$Title</a><% if not Last %>, <% else %>;<% end_if %>
<% end_loop %>
<% end_if %>
<% if $Comments.exists %>
<a href="{$Link}#comments-holder">
<%t Blog.Comments "Comments" %>
$Comments.count
</a>;
<% end_if %>
<%t Blog.Posted "Posted" %>
<a href="$MonthlyArchiveLink">$PublishDate.ago</a>
<% 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 %>
<a href="$URL">$Name.XML</a>
<% else %>
$Name.XML
<% end_if %>
<% end_loop %>
<% end_if %>
</p>

View File

@ -3,7 +3,7 @@
<div>
<% if $CurrentProfile.BlogProfileImage %>
<div class="profile-image">
$CurrentProfile.BlogProfileImage.setWidth(180)
$CurrentProfile.BlogProfileImage.ScaleWidth(180)
</div>
<% end_if %>
<div class="profile-summary">

View File

@ -8,7 +8,7 @@
<p class="post-image">
<a href="$Link" title="<%t Blog.ReadMoreAbout "Read more about '{title}'..." title=$Title %>">
$FeaturedImage.setWidth(795)
$FeaturedImage.ScaleWidth(795)
</a>
</p>

View File

@ -1,4 +1,4 @@
<div class="blog-admin-outer">
<%-- this resets ths template (from TabSet_holder) and renders using forTemplate --%>
$setTemplate('')
</div>
<div class="blog-admin-outer">
<%-- this resets ths template (from TabSet_holder) and renders using forTemplate --%>
$setTemplate('')
</div>

View File

@ -1,20 +1,20 @@
<% require themedCSS('blog', 'blog') %>
<div class="blog-entry content-container <% if $SideBarView %>unit size3of4<% end_if %>">
<article>
<h1>$Title</h1>
<% if $FeaturedImage %>
<p class="post-image">$FeaturedImage.setWidth(795)</p>
<% end_if %>
<div class="content">$Content</div>
<% include EntryMeta %>
</article>
$Form
$CommentsForm
</div>
<% include BlogSideBar %>
<% require themedCSS('blog', 'blog') %>
<div class="blog-entry content-container <% if $SideBarView %>unit size3of4<% end_if %>">
<article>
<h1>$Title</h1>
<% if $FeaturedImage %>
<p class="post-image">$FeaturedImage.ScaleWidth(795)</p>
<% end_if %>
<div class="content">$Content</div>
<% include EntryMeta %>
</article>
$Form
$CommentsForm
</div>
<% include BlogSideBar %>

View File

@ -1,5 +1,17 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogCategory;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Blog\Model\BlogTag;
use SilverStripe\Control\Controller;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Member;
/**
* @mixin PHPUnit_Framework_TestCase
*/
@ -8,7 +20,7 @@ class BlogCategoryTest extends FunctionalTest
/**
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
/**
* {@inheritdoc}
@ -17,7 +29,7 @@ class BlogCategoryTest extends FunctionalTest
{
parent::setUp();
SS_Datetime::set_mock_now('2013-10-10 20:00:00');
DBDatetime::set_mock_now('2013-10-10 20:00:00');
}
/**
@ -25,7 +37,7 @@ class BlogCategoryTest extends FunctionalTest
*/
public function tearDown()
{
SS_Datetime::clear_mock_now();
DBDatetime::clear_mock_now();
parent::tearDown();
}
@ -42,12 +54,12 @@ class BlogCategoryTest extends FunctionalTest
$member->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']);
}
}
}

View File

@ -1,29 +1,41 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;
/**
* @mixin PHPUnit_Framework_TestCase
* @coversDefaultClass \SilverStripe\Blog\Model\BlogPostFilter
*/
class BlogPostFilterTest extends SapphireTest
{
/**
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
public function setUp()
{
parent::setUp();
SS_Datetime::set_mock_now('2013-10-10 20:00:00');
DBDatetime::set_mock_now('2013-10-10 20:00:00');
}
public function tearDown()
{
SS_Datetime::clear_mock_now();
DBDatetime::clear_mock_now();
parent::tearDown();
}
/**
* Tests that unpublished articles are not returned
* @covers ::augmentSQL
*/
public function testFilter()
{
$member = Member::currentUser();
@ -35,11 +47,11 @@ class BlogPostFilterTest extends SapphireTest
/**
* @var Blog $blog
*/
$blog = $this->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');
}

View File

@ -1,11 +1,17 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Dev\SapphireTest;
class BlogPostNotificationsTest extends SapphireTest
{
/**
* {@inheritDoc}
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
public function testUpdateNotificationRecipients()
{
@ -13,8 +19,8 @@ class BlogPostNotificationsTest extends SapphireTest
$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(
@ -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(

View File

@ -1,26 +1,27 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\Security\Member;
class BlogPostTest extends SapphireTest
{
/**
* {@inheritDoc}
* @var string
*/
public static $fixture_file = 'blog.yml';
/**
* {@inheritdoc}
*/
public function setUp()
{
parent::setUp();
}
protected static $fixture_file = 'blog.yml';
/**
* {@inheritdoc}
*/
public function tearDown()
{
SS_Datetime::clear_mock_now();
DBDatetime::clear_mock_now();
parent::tearDown();
}
@ -29,12 +30,15 @@ class BlogPostTest extends SapphireTest
*/
public function testCanView($date, $user, $page, $canView)
{
$userRecord = $this->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());
}
}

View File

@ -1,14 +1,26 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\Blog\Model\BlogTag;
use SilverStripe\Control\Controller;
use SilverStripe\Dev\FunctionalTest;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\ValidationException;
use SilverStripe\Security\Member;
/**
* @mixin PHPUnit_Framework_TestCase
*/
class BlogTagTest extends FunctionalTest
{
/**
* {@inheritDoc}
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
/**
* {@inheritdoc}
@ -17,7 +29,7 @@ class BlogTagTest extends FunctionalTest
{
parent::setUp();
SS_Datetime::set_mock_now('2013-10-10 20:00:00');
DBDatetime::set_mock_now('2013-10-10 20:00:00');
}
/**
@ -25,7 +37,7 @@ class BlogTagTest extends FunctionalTest
*/
public function tearDown()
{
SS_Datetime::clear_mock_now();
DBDatetime::clear_mock_now();
parent::tearDown();
}
@ -42,12 +54,12 @@ class BlogTagTest extends FunctionalTest
$member->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']);
}
}
}

View File

@ -1,14 +1,23 @@
<?php
class BlogTagsCloudWidgetTest extends SapphireTest {
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Widgets\BlogTagsCloudWidget;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
class BlogTagsCloudWidgetTest extends SapphireTest
{
/**
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
public function testGetCMSFields() {
if (!class_exists('Widget')) {
public function testGetCMSFields()
{
if (!class_exists('SilverStripe\\Widgets\\Model\\Widget')) {
$this->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();

View File

@ -1,5 +1,21 @@
<?php
namespace SilverStripe\Blog\Tests;
use SilverStripe\Blog\Model\Blog;
use SilverStripe\Blog\Model\BlogController;
use SilverStripe\Blog\Model\BlogPost;
use SilverStripe\CMS\Controllers\ContentController;
use SilverStripe\Control\Controller;
use SilverStripe\Control\Director;
use SilverStripe\Control\HTTPRequest;
use SilverStripe\Core\Config\Config;
use SilverStripe\Dev\SapphireTest;
use SilverStripe\ORM\DataModel;
use SilverStripe\ORM\FieldType\DBDatetime;
use SilverStripe\ORM\SS_List;
use SilverStripe\Security\Member;
/**
* @mixin PHPUnit_Framework_TestCase
*/
@ -8,7 +24,7 @@ class BlogTest extends SapphireTest
/**
* @var string
*/
public static $fixture_file = 'blog.yml';
protected static $fixture_file = 'blog.yml';
/**
* {@inheritdoc}
@ -18,12 +34,12 @@ class BlogTest extends SapphireTest
parent::setUp();
Config::nest();
SS_Datetime::set_mock_now('2013-10-10 20:00:00');
DBDatetime::set_mock_now('2013-10-10 20:00:00');
/**
* @var Blog $blog
*/
$blog = $this->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());
}

View File

@ -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