From 6ebbd9e1ac8a90e4e6144a6a9eb972860ffc7497 Mon Sep 17 00:00:00 2001 From: Damian Mooyman Date: Wed, 8 Apr 2015 17:55:19 +1200 Subject: [PATCH] API Allow commenting options to be set per-page in CMS --- _config/comments.yml | 9 ++ code/controllers/CommentingController.php | 44 +++--- code/extensions/CommentsExtension.php | 165 ++++++++++++++++++---- docs/en/Configuration.md | 45 +++--- tests/CommentsTest.php | 124 ++++++++++++++-- tests/CommentsTest.yml | 3 + 6 files changed, 316 insertions(+), 74 deletions(-) create mode 100644 _config/comments.yml diff --git a/_config/comments.yml b/_config/comments.yml new file mode 100644 index 0000000..cd0abb2 --- /dev/null +++ b/_config/comments.yml @@ -0,0 +1,9 @@ +--- +# SiteTree specific config for comments +Name: commentssitetree +only: + moduleexists: 'cms' +--- +SiteTree: + comments: + enabled_cms: true diff --git a/code/controllers/CommentingController.php b/code/controllers/CommentingController.php index 704ef06..b7d3b4d 100644 --- a/code/controllers/CommentingController.php +++ b/code/controllers/CommentingController.php @@ -360,22 +360,21 @@ class CommentingController extends Controller { // create the comment form $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($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 // which in here, it hasn't been. $form->loadDataFrom(array( @@ -453,11 +452,18 @@ class CommentingController extends Controller { $form->Fields()->push(new HiddenField("AuthorID", "Author ID", $member->ID)); } - // is moderation turned on - $requireModeration = $this->getOption('require_moderation'); - if(!$requireModeration) { - $requireModerationNonmembers = $this->getOption('require_moderation_nonmembers'); - $requireModeration = $requireModerationNonmembers ? !Member::currentUser() : false; + // What kind of moderation is required? + switch($this->getOwnerRecord()->ModerationRequired) { + case 'Required': + $requireModeration = true; + break; + case 'NonMembersOnly': + $requireModeration = empty($member); + break; + case 'None': + default: + $requireModeration = false; + break; } // we want to show a notification if comments are moderated diff --git a/code/extensions/CommentsExtension.php b/code/extensions/CommentsExtension.php index c13ba58..a8950c6 100644 --- a/code/extensions/CommentsExtension.php +++ b/code/extensions/CommentsExtension.php @@ -16,34 +16,56 @@ class CommentsExtension extends DataExtension { */ private static $comments = array( '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 - '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 '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_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 - '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", 'comments_per_page' => 10, 'comments_holder_id' => "comments-holder", // id for the comments holder 'comment_permalink_prefix' => "comment-", // id prefix for each comment. If needed make this different - 'require_moderation' => false, - 'require_moderation_nonmembers' => false, // requires moderation for comments posted by non-members. 'require_moderation' overrides this if set. + 'require_moderation' => false, // Require moderation for all comments + // 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_elements' => array('a', 'img', 'i', 'b'), 'use_preview' => false, // preview formatted comment (when allowing HTML). Requires include_js=true ); - public static function get_extra_config($class, $extension, $args = null) { - $config = array(); - - // if it is attached to the SiteTree then we need to add ProvideComments - if(is_subclass_of($class, 'SiteTree') || $class == 'SiteTree') { - $config['db'] = array('ProvideComments' => 'Boolean'); + private static $db = array( + 'ProvideComments' => 'Boolean', + 'ModerationRequired' => "Enum('None,Required,NonMembersOnly','None')", + 'CommentsRequireLogin' => '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 */ public function updateSettingsFields(FieldList $fields) { - if($this->attachedToSiteTree()) { - $fields->addFieldToTab('Root.Settings', - new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments')) + + $options = FieldGroup::create()->setTitle(_t('CommentsExtension.COMMENTOPTIONS', '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'); 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 * @@ -95,9 +185,7 @@ class CommentsExtension extends DataExtension { ->filter('IsSpam', 0); // Filter unmoderated comments for non-administrators if moderation is enabled - if ($this->owner->getCommentsOption('require_moderation') - || $this->owner->getCommentsOption('require_moderation_nonmembers') - ) { + if ($this->owner->ModerationRequired !== 'None') { $list = $list->filter('Moderated', 1); } @@ -142,10 +230,12 @@ class CommentsExtension extends DataExtension { * @return boolean */ public function getCommentsEnabled() { - if(!$this->owner->getCommentsOption('enabled')) return false; - - // Non-page objects always have comments enabled - return !$this->attachedToSiteTree() || $this->owner->ProvideComments; + // Determine which flag should be used to determine if this is enabled + if($this->owner->getCommentsOption('enabled_cms')) { + return $this->owner->ProvideComments; + } else { + return $this->owner->getCommentsOption('enabled'); + } } /** @@ -186,8 +276,11 @@ class CommentsExtension extends DataExtension { * @return boolean */ public function canPostComment($member = null) { + // Deny if not enabled for this object + if(!$this->owner->CommentsEnabled) return false; + // Check if member is required - $requireLogin = $this->owner->getCommentsOption('require_login'); + $requireLogin = $this->owner->CommentsRequireLogin; if(!$requireLogin) return true; // Check member is logged in @@ -195,7 +288,7 @@ class CommentsExtension extends DataExtension { if(!$member) return false; // If member required check permissions - $requiredPermission = $this->getPostingRequiredPermission(); + $requiredPermission = $this->owner->PostingRequiredPermission; if($requiredPermission && !Permission::checkMember($member, $requiredPermission)) return false; return true; @@ -323,10 +416,12 @@ class CommentsExtension extends DataExtension { return $value; } - public function updateCMSFields(\FieldList $fields) { - // Disable moderation if not permitted - if(!$this->owner->canModerateComments()) return; - + /** + * Add moderation functions to the current fieldlist + * + * @param FieldList $fields + */ + protected function updateModerationFields(FieldList $fields) { // Create gridfield config $commentsConfig = CommentsGridFieldConfig::create(); @@ -360,4 +455,16 @@ class CommentsExtension extends DataExtension { $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); + } + } } diff --git a/docs/en/Configuration.md b/docs/en/Configuration.md index 136d1ba..cc0a3ea 100644 --- a/docs/en/Configuration.md +++ b/docs/en/Configuration.md @@ -24,26 +24,35 @@ SiteTree: extensions: - CommentsExtension comments: - require_login: false # boolean, whether a user needs to login - required_permission: false # required permission to comment (or array of permissions) - include_js: true # Enhance operation by ajax behaviour on moderation links - show_comments_when_disabled: false # when comments are disabled should we show older comments (if available) - order_comments_by: '"Created" DESC' - comments_per_page: 10 - comments_holder_id: 'comments-holder' # id for the comments holder - comment_permalink_prefix: 'comment-' # id prefix for each comment. If needed make this different - require_moderation: false - 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 - use_gravatar: false - gravatar_size: 80 + enabled: true # Enables commenting to be disabled for a specific class (or subclass of a parent with commenting enabled) + enabled_cms: false # The 'enabled' option will be set via the CMS instead of config + require_login: false # boolean, whether a user needs to login + require_login_cms: false # The 'require_login' option will be set via the CMS instead of config + required_permission: false # required permission to comment (or array of permissions) + include_js: true # Enhance operation by ajax behaviour on moderation links + 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_default: 'identicon' # theme for 'not found' gravatar (see http://gravatar.com/site/implement/images/) + 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) + order_comments_by: '"Created" DESC' + comments_per_page: 10 + comments_holder_id: 'comments-holder' # id for the comments holder + comment_permalink_prefix: 'comment-' # id prefix for each comment. If needed make this different + require_moderation: false + require_moderation_nonmembers: false # requires moderation for comments posted by non-members. 'require_moderation' overrides this if set. + 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 the extension (or on the built-in SiteTree commenting) use `set_config_value` diff --git a/tests/CommentsTest.php b/tests/CommentsTest.php index c3d959d..3255340 100644 --- a/tests/CommentsTest.php +++ b/tests/CommentsTest.php @@ -22,11 +22,16 @@ class CommentsTest extends FunctionalTest { } 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 - 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'); + $this->assertEquals('None', $item->ModerationRequired); $this->assertDOSEquals(array( array('Name' => 'Comment 1'), @@ -34,23 +39,126 @@ class CommentsTest extends FunctionalTest { ), $item->Comments(), 'Only 2 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)); + $this->assertEquals('Required', $item->ModerationRequired); $this->assertDOSEquals(array( array('Name' => 'Comment 3') ), $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()); + // require_moderation_nonmembers still filters out unmoderated comments 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()); } + /** + * 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() { $visitor = $this->objFromFixture('Member', 'visitor'); $admin = $this->objFromFixture('Member', 'commentadmin'); diff --git a/tests/CommentsTest.yml b/tests/CommentsTest.yml index 55543ae..6ea339a 100644 --- a/tests/CommentsTest.yml +++ b/tests/CommentsTest.yml @@ -20,9 +20,11 @@ CommentableItem: first: Title: First ProvideComments: 1 + CommentsRequireLogin: 0 second: Title: Second ProvideComments: 1 + CommentsRequireLogin: 1 third: Title: Third ProvideComments: 1 @@ -32,6 +34,7 @@ CommentableItem: spammed: ProvideComments: 1 Title: spammed + ModerationRequired: 'None' Comment: firstComA: