API Allow commenting options to be set per-page in CMS

This commit is contained in:
Damian Mooyman 2015-04-08 17:55:19 +12:00
parent f9c93ef9aa
commit 6ebbd9e1ac
6 changed files with 316 additions and 74 deletions

9
_config/comments.yml Normal file
View File

@ -0,0 +1,9 @@
---
# SiteTree specific config for comments
Name: commentssitetree
only:
moduleexists: 'cms'
---
SiteTree:
comments:
enabled_cms: true

View File

@ -360,22 +360,21 @@ class CommentingController extends Controller {
// create the comment form // create the comment form
$form = new Form($this, 'CommentsForm', $fields, $actions, $required); $form = new Form($this, 'CommentsForm', $fields, $actions, $required);
// Load member data
$requireLogin = $this->getOption('require_login');
$permission = $this->getOption('required_permission');
$member = Member::currentUser();
if(($requireLogin || $permission) && $member) {
$fields = $form->Fields();
$fields->removeByName('Name');
$fields->removeByName('Email');
$fields->insertBefore(new ReadonlyField("NameView", _t('CommentInterface.YOURNAME', 'Your name'), $member->getName()), 'URL');
$fields->push(new HiddenField("Name", "", $member->getName()));
$fields->push(new HiddenField("Email", "", $member->Email));
}
// if the record exists load the extra required data // if the record exists load the extra required data
if($record = $this->getOwnerRecord()) { if($record = $this->getOwnerRecord()) {
// Load member data
$member = Member::currentUser();
if(($record->CommentsRequireLogin || $record->PostingRequiredPermission) && $member) {
$fields = $form->Fields();
$fields->removeByName('Name');
$fields->removeByName('Email');
$fields->insertBefore(new ReadonlyField("NameView", _t('CommentInterface.YOURNAME', 'Your name'), $member->getName()), 'URL');
$fields->push(new HiddenField("Name", "", $member->getName()));
$fields->push(new HiddenField("Email", "", $member->Email));
}
// we do not want to read a new URL when the form has already been submitted // we do not want to read a new URL when the form has already been submitted
// which in here, it hasn't been. // which in here, it hasn't been.
$form->loadDataFrom(array( $form->loadDataFrom(array(
@ -453,11 +452,18 @@ class CommentingController extends Controller {
$form->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID)); $form->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID));
} }
// is moderation turned on // What kind of moderation is required?
$requireModeration = $this->getOption('require_moderation'); switch($this->getOwnerRecord()->ModerationRequired) {
if(!$requireModeration) { case 'Required':
$requireModerationNonmembers = $this->getOption('require_moderation_nonmembers'); $requireModeration = true;
$requireModeration = $requireModerationNonmembers ? !Member::currentUser() : false; break;
case 'NonMembersOnly':
$requireModeration = empty($member);
break;
case 'None':
default:
$requireModeration = false;
break;
} }
// we want to show a notification if comments are moderated // we want to show a notification if comments are moderated

View File

@ -16,34 +16,56 @@ class CommentsExtension extends DataExtension {
*/ */
private static $comments = array( private static $comments = array(
'enabled' => true, // Allows commenting to be disabled even if the extension is present 'enabled' => true, // Allows commenting to be disabled even if the extension is present
'enabled_cms' => false, // Allows commenting to be enabled or disabled via the CMS
'require_login' => false, // boolean, whether a user needs to login 'require_login' => false, // boolean, whether a user needs to login
'required_permission' => false, // required permission to comment (or array of permissions) 'require_login_cms' => false, // Allows require_login to be set via the CMS
// required permission to comment (or array of permissions). require_login must be set for this to work
'required_permission' => false,
'include_js' => true, // Enhance operation by ajax behaviour on moderation links 'include_js' => true, // Enhance operation by ajax behaviour on moderation links
'use_gravatar' => false, // set to true to show gravatar icons, 'use_gravatar' => false, // set to true to show gravatar icons,
'gravatar_size' => 80, // size of gravatar in pixels. This is the same as the standard default 'gravatar_size' => 80, // size of gravatar in pixels. This is the same as the standard default
'gravatar_default' => 'identicon', // theme for 'not found' gravatar (see http://gravatar.com/site/implement/images/) // theme for 'not found' gravatar (see http://gravatar.com/site/implement/images/)
'gravatar_default' => 'identicon',
'gravatar_rating' => 'g', // gravatar rating. This is the same as the standard default 'gravatar_rating' => 'g', // gravatar rating. This is the same as the standard default
'show_comments_when_disabled' => false, // when comments are disabled should we show older comments (if available) // when comments are disabled should we show older comments (if available)
'show_comments_when_disabled' => false,
'order_comments_by' => "\"Created\" DESC", 'order_comments_by' => "\"Created\" DESC",
'comments_per_page' => 10, 'comments_per_page' => 10,
'comments_holder_id' => "comments-holder", // id for the comments holder 'comments_holder_id' => "comments-holder", // id for the comments holder
'comment_permalink_prefix' => "comment-", // id prefix for each comment. If needed make this different 'comment_permalink_prefix' => "comment-", // id prefix for each comment. If needed make this different
'require_moderation' => false, 'require_moderation' => false, // Require moderation for all comments
'require_moderation_nonmembers' => false, // requires moderation for comments posted by non-members. 'require_moderation' overrides this if set. // requires moderation for comments posted by non-members. 'require_moderation' overrides this if set.
'require_moderation_nonmembers' => false,
// If true, ignore above values and configure moderation requirements via the CMS only
'require_moderation_cms' => false,
'html_allowed' => false, // allow for sanitized HTML in comments 'html_allowed' => false, // allow for sanitized HTML in comments
'html_allowed_elements' => array('a', 'img', 'i', 'b'), 'html_allowed_elements' => array('a', 'img', 'i', 'b'),
'use_preview' => false, // preview formatted comment (when allowing HTML). Requires include_js=true 'use_preview' => false, // preview formatted comment (when allowing HTML). Requires include_js=true
); );
public static function get_extra_config($class, $extension, $args = null) { private static $db = array(
$config = array(); 'ProvideComments' => 'Boolean',
'ModerationRequired' => "Enum('None,Required,NonMembersOnly','None')",
// if it is attached to the SiteTree then we need to add ProvideComments 'CommentsRequireLogin' => 'Boolean',
if(is_subclass_of($class, 'SiteTree') || $class == 'SiteTree') { );
$config['db'] = array('ProvideComments' => 'Boolean');
/**
* CMS configurable options should default to the config values
*/
public function populateDefaults() {
// Set if comments should be enabled by default
$this->owner->ProvideComments = $this->owner->getCommentsOption('enabled') ? 1 : 0;
// If moderations options should be configurable via the CMS then
if($this->owner->getCommentsOption('require_moderation')) {
$this->owner->ModerationRequired = 'Required';
} elseif($this->owner->getCommentsOption('require_moderation_nonmembers')) {
$this->owner->ModerationRequired = 'NonMembersOnly';
} else {
$this->owner->ModerationRequired = 'None';
} }
return $config; $this->owner->CommentsRequireLogin = $this->owner->getCommentsOption('require_login') ? 1 : 0;
} }
@ -57,11 +79,48 @@ class CommentsExtension extends DataExtension {
* @param FieldSet * @param FieldSet
*/ */
public function updateSettingsFields(FieldList $fields) { public function updateSettingsFields(FieldList $fields) {
if($this->attachedToSiteTree()) {
$fields->addFieldToTab('Root.Settings', $options = FieldGroup::create()->setTitle(_t('CommentsExtension.COMMENTOPTIONS', 'Comments'));
new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments'))
// Check if enabled setting should be cms configurable
if($this->owner->getCommentsOption('enabled_cms')) {
$options->push(new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments')));
}
// Check if we should require users to login to comment
if($this->owner->getCommentsOption('require_login_cms')) {
$options->push(
new CheckboxField(
'CommentsRequireLogin',
_t('Comments.COMMENTSREQUIRELOGIN', 'Require login to comment')
)
); );
} }
if($options->FieldList()->count()) {
if($fields->hasTabSet()) {
$fields->addFieldsToTab('Root.Settings', $options);
} else {
$fields->push($options);
}
}
// Check if moderation should be enabled via cms configurable
if($this->owner->getCommentsOption('require_moderation_cms')) {
$moderationField = new DropdownField('ModerationRequired', 'Comment Moderation', array(
'None' => _t('CommentsExtension.MODERATIONREQUIRED_NONE', 'No moderation required'),
'Required' => _t('CommentsExtension.MODERATIONREQUIRED_REQUIRED', 'Moderate all comments'),
'NonMembersOnly' => _t(
'CommentsExtension.MODERATIONREQUIRED_NONMEMBERSONLY',
'Only moderate non-members'
),
));
if($fields->hasTabSet()) {
$fields->addFieldsToTab('Root.Settings', $moderationField);
} else {
$fields->push($moderationField);
}
}
} }
/** /**
@ -80,7 +139,38 @@ class CommentsExtension extends DataExtension {
Deprecation::notice('2.0', 'Use PagedComments to get paged coments'); Deprecation::notice('2.0', 'Use PagedComments to get paged coments');
return $this->PagedComments(); return $this->PagedComments();
} }
/**
* Get comment moderation rules for this parent
*
* @return string A value of either 'None' (no moderation required), 'Required' (all comments),
* or 'NonMembersOnly' (only not logged in users)
*/
public function getModerationRequired() {
if($this->owner->getCommentsOption('require_moderation_cms')) {
return $this->owner->getField('ModerationRequired');
} elseif($this->owner->getCommentsOption('require_moderation')) {
return 'Required';
} elseif($this->owner->getCommentsOption('require_moderation_nonmembers')) {
return 'NonMembersOnly';
} else {
return 'None';
}
}
/**
* Determine if users must be logged in to post comments
*
* @return boolean
*/
public function getCommentsRequireLogin() {
if($this->owner->getCommentsOption('require_login_cms')) {
return (bool)$this->owner->getField('CommentsRequireLogin');
} else {
return (bool)$this->owner->getCommentsOption('require_login');
}
}
/** /**
* Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend * Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend
* *
@ -95,9 +185,7 @@ class CommentsExtension extends DataExtension {
->filter('IsSpam', 0); ->filter('IsSpam', 0);
// Filter unmoderated comments for non-administrators if moderation is enabled // Filter unmoderated comments for non-administrators if moderation is enabled
if ($this->owner->getCommentsOption('require_moderation') if ($this->owner->ModerationRequired !== 'None') {
|| $this->owner->getCommentsOption('require_moderation_nonmembers')
) {
$list = $list->filter('Moderated', 1); $list = $list->filter('Moderated', 1);
} }
@ -142,10 +230,12 @@ class CommentsExtension extends DataExtension {
* @return boolean * @return boolean
*/ */
public function getCommentsEnabled() { public function getCommentsEnabled() {
if(!$this->owner->getCommentsOption('enabled')) return false; // Determine which flag should be used to determine if this is enabled
if($this->owner->getCommentsOption('enabled_cms')) {
// Non-page objects always have comments enabled return $this->owner->ProvideComments;
return !$this->attachedToSiteTree() || $this->owner->ProvideComments; } else {
return $this->owner->getCommentsOption('enabled');
}
} }
/** /**
@ -186,8 +276,11 @@ class CommentsExtension extends DataExtension {
* @return boolean * @return boolean
*/ */
public function canPostComment($member = null) { public function canPostComment($member = null) {
// Deny if not enabled for this object
if(!$this->owner->CommentsEnabled) return false;
// Check if member is required // Check if member is required
$requireLogin = $this->owner->getCommentsOption('require_login'); $requireLogin = $this->owner->CommentsRequireLogin;
if(!$requireLogin) return true; if(!$requireLogin) return true;
// Check member is logged in // Check member is logged in
@ -195,7 +288,7 @@ class CommentsExtension extends DataExtension {
if(!$member) return false; if(!$member) return false;
// If member required check permissions // If member required check permissions
$requiredPermission = $this->getPostingRequiredPermission(); $requiredPermission = $this->owner->PostingRequiredPermission;
if($requiredPermission && !Permission::checkMember($member, $requiredPermission)) return false; if($requiredPermission && !Permission::checkMember($member, $requiredPermission)) return false;
return true; return true;
@ -323,10 +416,12 @@ class CommentsExtension extends DataExtension {
return $value; return $value;
} }
public function updateCMSFields(\FieldList $fields) { /**
// Disable moderation if not permitted * Add moderation functions to the current fieldlist
if(!$this->owner->canModerateComments()) return; *
* @param FieldList $fields
*/
protected function updateModerationFields(FieldList $fields) {
// Create gridfield config // Create gridfield config
$commentsConfig = CommentsGridFieldConfig::create(); $commentsConfig = CommentsGridFieldConfig::create();
@ -360,4 +455,16 @@ class CommentsExtension extends DataExtension {
$fields->push($moderated); $fields->push($moderated);
} }
} }
public function updateCMSFields(\FieldList $fields) {
// Disable moderation if not permitted
if($this->owner->canModerateComments()) {
$this->updateModerationFields($fields);
}
// If this isn't a page we should merge the settings into the CMS fields
if(!$this->attachedToSiteTree()) {
$this->updateSettingsFields($fields);
}
}
} }

View File

@ -24,26 +24,35 @@ SiteTree:
extensions: extensions:
- CommentsExtension - CommentsExtension
comments: comments:
require_login: false # boolean, whether a user needs to login enabled: true # Enables commenting to be disabled for a specific class (or subclass of a parent with commenting enabled)
required_permission: false # required permission to comment (or array of permissions) enabled_cms: false # The 'enabled' option will be set via the CMS instead of config
include_js: true # Enhance operation by ajax behaviour on moderation links require_login: false # boolean, whether a user needs to login
show_comments_when_disabled: false # when comments are disabled should we show older comments (if available) require_login_cms: false # The 'require_login' option will be set via the CMS instead of config
order_comments_by: '"Created" DESC' required_permission: false # required permission to comment (or array of permissions)
comments_per_page: 10 include_js: true # Enhance operation by ajax behaviour on moderation links
comments_holder_id: 'comments-holder' # id for the comments holder use_gravatar; false # set to true to show gravatar icons,
comment_permalink_prefix: 'comment-' # id prefix for each comment. If needed make this different gravatar_size: 80 # size of gravatar in pixels. This is the same as the standard default
require_moderation: false gravatar_default: 'identicon' # theme for 'not found' gravatar (see http://gravatar.com/site/implement/images/)
html_allowed: false # allow for sanitized HTML in comments gravatar_rating: 'g' # gravatar rating. This is the same as the standard default
html_allowed_elements: show_comments_when_disabled: false # when comments are disabled should we show older comments (if available)
- a order_comments_by: '"Created" DESC'
- img comments_per_page: 10
- i comments_holder_id: 'comments-holder' # id for the comments holder
- b comment_permalink_prefix: 'comment-' # id prefix for each comment. If needed make this different
use_preview: false # preview formatted comment (when allowing HTML). Requires include_js=true require_moderation: false
use_gravatar: false require_moderation_nonmembers: false # requires moderation for comments posted by non-members. 'require_moderation' overrides this if set.
gravatar_size: 80 require_moderation_cms: false # If true, ignore above values and configure moderation requirements via the CMS only
html_allowed: false # allow for sanitized HTML in comments
html_allowed_elements:
- a
- img
- i
- b
use_preview: false # preview formatted comment (when allowing HTML). Requires include_js=true
``` ```
Enabling any of the *_cms options will instead allow these options to be configured under the settings tab
of each page in the CMS.
If you want to customize any of the configuration options after you have added If you want to customize any of the configuration options after you have added
the extension (or on the built-in SiteTree commenting) use `set_config_value` the extension (or on the built-in SiteTree commenting) use `set_config_value`

View File

@ -22,11 +22,16 @@ class CommentsTest extends FunctionalTest {
} }
public function testCommentsList() { public function testCommentsList() {
// comments don't require moderation so unmoderated comments can be // comments don't require moderation so unmoderated comments can be
// shown but not spam posts // shown but not spam posts
Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => false)); Config::inst()->update('CommentableItem', 'comments', array(
'require_moderation_nonmembers' => false,
'require_moderation' => false,
'require_moderation_cms' => false,
));
$item = $this->objFromFixture('CommentableItem', 'spammed'); $item = $this->objFromFixture('CommentableItem', 'spammed');
$this->assertEquals('None', $item->ModerationRequired);
$this->assertDOSEquals(array( $this->assertDOSEquals(array(
array('Name' => 'Comment 1'), array('Name' => 'Comment 1'),
@ -34,23 +39,126 @@ class CommentsTest extends FunctionalTest {
), $item->Comments(), 'Only 2 non spam posts should be shown'); ), $item->Comments(), 'Only 2 non spam posts should be shown');
// when moderated, only moderated, non spam posts should be shown. // when moderated, only moderated, non spam posts should be shown.
Config::inst()->update('CommentableItem', 'comments', array('require_moderation_nonmembers' => true));
$this->assertEquals('NonMembersOnly', $item->ModerationRequired);
// Check that require_moderation overrides this option
Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => true)); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => true));
$this->assertEquals('Required', $item->ModerationRequired);
$this->assertDOSEquals(array( $this->assertDOSEquals(array(
array('Name' => 'Comment 3') array('Name' => 'Comment 3')
), $item->Comments(), 'Only 1 non spam, moderated post should be shown'); ), $item->Comments(), 'Only 1 non spam, moderated post should be shown');
// As of 2.0, logging in with admin no longer grants special privileges to view frontend comments and should
// be done via the CMS
$this->logInWithPermission('CMS_ACCESS_CommentAdmin');
Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => true));
$this->assertEquals(1, $item->Comments()->Count()); $this->assertEquals(1, $item->Comments()->Count());
// require_moderation_nonmembers still filters out unmoderated comments
Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => false)); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => false));
$this->assertEquals(1, $item->Comments()->Count());
Config::inst()->update('CommentableItem', 'comments', array('require_moderation_nonmembers' => false));
$this->assertEquals(2, $item->Comments()->Count()); $this->assertEquals(2, $item->Comments()->Count());
} }
/**
* Test moderation options configured via the CMS
*/
public function testCommentCMSModerationList() {
// comments don't require moderation so unmoderated comments can be
// shown but not spam posts
Config::inst()->update('CommentableItem', 'comments', array(
'require_moderation' => true,
'require_moderation_cms' => true,
));
$item = $this->objFromFixture('CommentableItem', 'spammed');
$this->assertEquals('None', $item->ModerationRequired);
$this->assertDOSEquals(array(
array('Name' => 'Comment 1'),
array('Name' => 'Comment 3')
), $item->Comments(), 'Only 2 non spam posts should be shown');
// when moderated, only moderated, non spam posts should be shown.
$item->ModerationRequired = 'NonMembersOnly';
$item->write();
$this->assertEquals('NonMembersOnly', $item->ModerationRequired);
// Check that require_moderation overrides this option
$item->ModerationRequired = 'Required';
$item->write();
$this->assertEquals('Required', $item->ModerationRequired);
$this->assertDOSEquals(array(
array('Name' => 'Comment 3')
), $item->Comments(), 'Only 1 non spam, moderated post should be shown');
$this->assertEquals(1, $item->Comments()->Count());
// require_moderation_nonmembers still filters out unmoderated comments
$item->ModerationRequired = 'NonMembersOnly';
$item->write();
$this->assertEquals(1, $item->Comments()->Count());
$item->ModerationRequired = 'None';
$item->write();
$this->assertEquals(2, $item->Comments()->Count());
}
public function testCanPostComment() {
Config::inst()->update('CommentableItem', 'comments', array(
'require_login' => false,
'require_login_cms' => false,
'required_permission' => false,
));
$item = $this->objFromFixture('CommentableItem', 'first');
$item2 = $this->objFromFixture('CommentableItem', 'second');
// Test restriction free commenting
if($member = Member::currentUser()) $member->logOut();
$this->assertFalse($item->CommentsRequireLogin);
$this->assertTrue($item->canPostComment());
// Test permission required to post
Config::inst()->update('CommentableItem', 'comments', array(
'require_login' => true,
'required_permission' => 'POSTING_PERMISSION',
));
$this->assertTrue($item->CommentsRequireLogin);
$this->assertFalse($item->canPostComment());
$this->logInWithPermission('WRONG_ONE');
$this->assertFalse($item->canPostComment());
$this->logInWithPermission('POSTING_PERMISSION');
$this->assertTrue($item->canPostComment());
$this->logInWithPermission('ADMIN');
$this->assertTrue($item->canPostComment());
// Test require login to post, but not any permissions
Config::inst()->update('CommentableItem', 'comments', array(
'required_permission' => false,
));
$this->assertTrue($item->CommentsRequireLogin);
if($member = Member::currentUser()) $member->logOut();
$this->assertFalse($item->canPostComment());
$this->logInWithPermission('ANY_PERMISSION');
$this->assertTrue($item->canPostComment());
// Test options set via CMS
Config::inst()->update('CommentableItem', 'comments', array(
'require_login' => true,
'require_login_cms' => true,
));
$this->assertFalse($item->CommentsRequireLogin);
$this->assertTrue($item2->CommentsRequireLogin);
if($member = Member::currentUser()) $member->logOut();
$this->assertTrue($item->canPostComment());
$this->assertFalse($item2->canPostComment());
// Login grants permission to post
$this->logInWithPermission('ANY_PERMISSION');
$this->assertTrue($item->canPostComment());
$this->assertTrue($item2->canPostComment());
}
public function testCanView() { public function testCanView() {
$visitor = $this->objFromFixture('Member', 'visitor'); $visitor = $this->objFromFixture('Member', 'visitor');
$admin = $this->objFromFixture('Member', 'commentadmin'); $admin = $this->objFromFixture('Member', 'commentadmin');

View File

@ -20,9 +20,11 @@ CommentableItem:
first: first:
Title: First Title: First
ProvideComments: 1 ProvideComments: 1
CommentsRequireLogin: 0
second: second:
Title: Second Title: Second
ProvideComments: 1 ProvideComments: 1
CommentsRequireLogin: 1
third: third:
Title: Third Title: Third
ProvideComments: 1 ProvideComments: 1
@ -32,6 +34,7 @@ CommentableItem:
spammed: spammed:
ProvideComments: 1 ProvideComments: 1
Title: spammed Title: spammed
ModerationRequired: 'None'
Comment: Comment:
firstComA: firstComA: