API Move configuration of comments to the config API and enable per-instance configuration

Enable comment moderation per-page
This commit is contained in:
Damian Mooyman 2015-04-07 15:56:00 +12:00
parent 93414c5231
commit 1900ab14b5
16 changed files with 798 additions and 440 deletions

View File

@ -1,31 +1,3 @@
<?php <?php
/** Deprecation::notification_version('2.0', 'comments');
* Comments Default Configuration
*
* To enable comments on your own {@link DataObject}'s you need to
* call Commenting::add_comments($object_name, $settings);
*
* Where $object_name is the name of the subclass of DataObject you want
* to add the comments to and $settings is a map of configuration options
* and values
*
* Example: mysite/_config.php
*
* <code>
* // uses the default values
* Commenting::add('SiteTree');
*
* // set configuration
* Commenting::add('SiteTree', array(
* 'require_login' => true
* ));
* </code>
*
* To see all the configuration options read docs/en/Configuration.md or
* consult the Commenting class.
*/
if(Config::inst()->get('Commenting', 'sitetree_comments') && class_exists('SiteTree') && !Commenting::has_commenting('SiteTree')) {
Commenting::add('SiteTree');
}

View File

@ -7,4 +7,4 @@ Director:
# handle old 2.4 style urls # handle old 2.4 style urls
'CommentingController//$Action/$ID/$OtherID': 'CommentingController' 'CommentingController//$Action/$ID/$OtherID': 'CommentingController'
'PageComments/$Action/$ID/$OtherID': 'CommentingController' 'PageComments/$Action/$ID/$OtherID': 'CommentingController'
'PageComments_Controller/$Action/$ID/$OtherID': 'CommentingController' 'PageComments_Controller/$Action/$ID/$OtherID': 'CommentingController'

91
code/CommentList.php Normal file
View File

@ -0,0 +1,91 @@
<?php
/**
* Handles polymorphic relation for commentlist
*
* Uses elements of PolymorphicHasManyList in 3.2
*
* @author dmooyman
*/
class CommentList extends HasManyList {
/**
* Retrieve the name of the class this relation is filtered by
*
* @return string
*/
public function getForeignClass() {
return $this->dataQuery->getQueryParam('Foreign.Class');
}
public function __construct($parentClassName) {
parent::__construct('Comment', 'ParentID');
// Ensure underlying DataQuery globally references the class filter
$this->dataQuery->setQueryParam('Foreign.Class', $parentClassName);
// For queries with multiple foreign IDs (such as that generated by
// DataList::relation) the filter must be generalised to filter by subclasses
$classNames = Convert::raw2sql(ClassInfo::subclassesFor($parentClassName));
$this->dataQuery->where(sprintf(
"\"BaseClass\" IN ('%s')", implode("', '", $classNames)
));
}
/**
* Adds the item to this relation.
*
* @param Comment $item The comment to be added
*/
public function add($item) {
// Check item given
if(is_numeric($item)) {
$item = Comment::get()->byID($item);
}
if(!($item instanceof Comment)) {
throw new InvalidArgumentException("CommentList::add() expecting a Comment object, or ID value");
}
// Validate foreignID
$foreignID = $this->getForeignID();
if(!$foreignID || is_array($foreignID)) {
throw new InvalidArgumentException("CommentList::add() can't be called until a single foreign ID is set");
}
$item->ParentID = $foreignID;
$item->BaseClass = $this->getForeignClass();
$item->write();
}
/**
* Remove a Comment from this relation by clearing the foreign key. Does not actually delete the comment.
*
* @param Comment $item The Comment to be removed
*/
public function remove($item) {
// Check item given
if(is_numeric($item)) {
$item = Comment::get()->byID($item);
}
if(!($item instanceof Comment)) {
throw new InvalidArgumentException("CommentList::remove() expecting a Comment object, or ID",
E_USER_ERROR);
}
// Don't remove item with unrelated class key
$foreignClass = $this->getForeignClass();
$classNames = ClassInfo::subclassesFor($foreignClass);
if(!in_array($item->BaseClass, $classNames)) return;
// Don't remove item which doesn't belong to this list
$foreignID = $this->getForeignID();
if( empty($foreignID)
|| (is_array($foreignID) && in_array($item->ParentID, $foreignID))
|| $foreignID == $item->ParentID
) {
$item->ParentID = null;
$item->BaseClass = null;
$item->write();
}
}
}

View File

@ -9,180 +9,138 @@
* *
* For documentation on how to use this class see docs/en/Configuration.md * For documentation on how to use this class see docs/en/Configuration.md
* *
* @deprecated since version 2.0
*
* @package comments * @package comments
*/ */
class Commenting { class Commenting {
/**
* @var array map of enabled {@link DataObject} and related configuration
*/
private static $enabled_classes = array();
/**
* @config
* @var bool Whether to enable commenting on SiteTree objects by default
*/
private static $sitetree_comments = true;
/**
* @var array default configuration values
*/
private static $default_config = array(
'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
'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.
'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
);
/** /**
* Adds commenting to a {@link DataObject} * Adds commenting to a {@link DataObject}
* *
* @deprecated since version 2.0
*
* @param string classname to add commenting to * @param string classname to add commenting to
* @param array $setting Settings. See {@link self::$default_config} for * @param array $settings Settings. See {@link self::$default_config} for
* available settings * available settings
* *
* @throws InvalidArgumentException * @throws InvalidArgumentException
*/ */
public static function add($class, $settings = false) { public static function add($class, $settings = false) {
if($settings && !is_array($settings)) { Deprecation::notice('2.0', 'Using Commenting::add is deprecated. Please use the config API instead');
throw new InvalidArgumentException('$settings needs to be an array or null', E_USER_ERROR); Config::inst()->update($class, 'extensions', array('CommentsExtension'));
}
self::$enabled_classes[$class] = $settings;
$class::add_extension('CommentsExtension'); // Check if settings must be customised
if($settings === false) return;
if(!is_array($settings)) {
throw new InvalidArgumentException('$settings needs to be an array or null');
}
Config::inst()->update($class, 'comments', $settings);
} }
/** /**
* Removes commenting from a {@link DataObject}. Does not remove existing comments * Removes commenting from a {@link DataObject}. Does not remove existing comments
* but does remove the extension. * but does remove the extension.
* *
* @deprecated since version 2.0
*
* @param string $class Class to remove {@link CommentsExtension} from * @param string $class Class to remove {@link CommentsExtension} from
*/ */
public static function remove($class) { public static function remove($class) {
if(isset(self::$enabled_classes[$class])) { Deprecation::notice('2.0', 'Using Commenting::remove is deprecated. Please use the config API instead');
unset(self::$enabled_classes[$class]);
}
$class::remove_extension('CommentsExtension'); $class::remove_extension('CommentsExtension');
} }
/** /**
* Returns whether a given class name has commenting enabled * Returns whether a given class name has commenting enabled
* *
* @deprecated since version 2.0
*
* @return bool * @return bool
*/ */
public static function has_commenting($class) { public static function has_commenting($class) {
return (isset(self::$enabled_classes[$class])); Deprecation::notice('2.0', 'Using Commenting::has_commenting is deprecated. Please use the config API instead');
return $class::has_extension('CommentsExtension');
} }
/** /**
* Sets a value for a class of a given config setting. Passing 'all' as the class * Sets a value for a class of a given config setting. Passing 'all' as the class
* sets it for everything * sets it for everything
* *
* @deprecated since version 2.0
*
* @param string $class Class to set the value on. Passing 'all' will set it to all * @param string $class Class to set the value on. Passing 'all' will set it to all
* active mappings * active mappings
* @param string $key setting to change * @param string $key setting to change
* @param mixed $value value of the setting * @param mixed $value value of the setting
*/ */
public static function set_config_value($class, $key, $value = false) { public static function set_config_value($class, $key, $value = false) {
if($class == "all") { Deprecation::notice('2.0', 'Commenting::set_config_value is deprecated. Use the config api instead');
if($enabledClasses = self::$enabled_classes) { if($class === "all") $class = 'CommentsExtension';
foreach($enabledClasses as $enabled) { Config::inst()->update($class, 'comments', array($key => $value));
if(!is_array($enabled)) $enabled = array();
$enabled[$key] = $value;
}
}
}
else if(isset(self::$enabled_classes[$class])) {
if(!is_array(self::$enabled_classes[$class])) self::$enabled_classes[$class] = array();
self::$enabled_classes[$class][$key] = $value;
}
else {
throw new Exception("$class does not have commenting enabled", E_USER_ERROR);
}
} }
/** /**
* Returns a given config value for a commenting class * Returns a given config value for a commenting class
* *
* @deprecated since version 2.0
*
* @param string $class * @param string $class
* @param string $key config value to return * @param string $key config value to return
* *
* @throws Exception * @throws Exception
* @return mixed * @return mixed
*/ */
public static function get_config_value($class = null, $key) { public static function get_config_value($class, $key) {
if(!$class || isset(self::$enabled_classes[$class])) { Deprecation::notice(
// custom configuration '2.0',
if(isset(self::$enabled_classes[$class][$key])) return self::$enabled_classes[$class][$key]; 'Using Commenting::get_config_value is deprecated. Please use $parent->getCommentsOption() or '
. 'CommentingController::getOption() instead'
// default configuration );
if(isset(self::$default_config[$key])) return self::$default_config[$key];
// Get settings
// config value doesn't exist if(!$class) {
throw new Exception("Config ($key) is not a valid configuration value", E_USER_WARNING); $class = 'CommentsExtension';
} } elseif(!$class::has_extension('CommentsExtension')) {
else { throw new InvalidArgumentException("$class does not have commenting enabled");
throw new Exception("$class does not have commenting enabled", E_USER_ERROR);
} }
return singleton($class)->getCommentsOption($key);
} }
/** /**
* Determines whether a config value on the commenting extension * Determines whether a config value on the commenting extension
* matches a given value. * matches a given value.
* *
* @deprecated since version 2.0
*
* @param string $class * @param string $class
* @param string $key * @param string $key
* @param string $value Expected value * @param string $value Expected value
* * @return boolean
* @return bool
*/ */
public static function config_value_equals($class, $key, $value) { public static function config_value_equals($class, $key, $value) {
try { $check = self::get_config_value($class, $key);
$check = self::get_config_value($class, $key); if($check && ($check == $value)) return true;
if($check && ($check == $value)) return true;
}
catch(Exception $e) {}
return false;
} }
/** /**
* Return whether a user can post on a given commenting instance * Return whether a user can post on a given commenting instance
* *
* @deprecated since version 2.0
*
* @param string $class * @param string $class
* @return boolean true
*/ */
public static function can_member_post($class) { public static function can_member_post($class) {
Deprecation::notice('2.0', 'Use $instance->canPostComment() directly instead');
$member = Member::currentUser(); $member = Member::currentUser();
try { // Check permission
$login = self::get_config_value($class, 'require_login'); $permission = self::get_config_value($class, 'required_permission');
$permission = self::get_config_value($class, 'required_permission'); if($permission && !Permission::check($permission)) return false;
if($permission && !Permission::check($permission)) return false; // Check login required
$requireLogin = self::get_config_value($class, 'require_login');
if($login && !$member) return false; return !$requireLogin || $member;
}
catch(Exception $e) {}
return true;
} }
} }

View File

@ -46,37 +46,7 @@ class CommentAdmin extends LeftAndMain implements PermissionProvider {
return Security::permissionFailure($this); return Security::permissionFailure($this);
} }
$commentsConfig = GridFieldConfig::create()->addComponents( $commentsConfig = CommentsGridFieldConfig::create();
new GridFieldFilterHeader(),
$columns = new GridFieldDataColumns(),
new GridFieldSortableHeader(),
new GridFieldPaginator(25),
new GridFieldDeleteAction(),
new GridFieldDetailForm(),
new GridFieldExportButton(),
new GridFieldEditButton(),
new GridFieldDetailForm(),
$manager = new GridFieldBulkManager()
);
$manager->addBulkAction(
'markAsSpam', 'Mark as spam', 'CommentsGridFieldBulkAction_MarkAsSpam',
array(
'isAjax' => true,
'icon' => 'delete',
'isDestructive' => true
)
);
$columns->setFieldFormatting(array(
'ParentTitle' => function($value, &$item) {
return sprintf(
'<a href="%s" class="cms-panel-link external-link action" target="_blank">%s</a>',
Convert::raw2xml($item->Link()),
Convert::raw2xml($value)
);
}
));
$needs = new GridField( $needs = new GridField(
'Comments', 'Comments',
@ -87,7 +57,7 @@ class CommentAdmin extends LeftAndMain implements PermissionProvider {
$moderated = new GridField( $moderated = new GridField(
'CommentsModerated', 'CommentsModerated',
_t('CommentsAdmin.CommentsModerated'), _t('CommentsAdmin.Moderated', 'Moderated'),
Comment::get()->filter('Moderated',1), Comment::get()->filter('Moderated',1),
$commentsConfig $commentsConfig
); );

View File

@ -0,0 +1,33 @@
<?php
class CommentsGridFieldConfig extends GridFieldConfig_RecordEditor {
public function __construct($itemsPerPage = 25) {
parent::__construct($itemsPerPage);
$this->addComponent(new GridFieldExportButton());
// Format column
$columns = $this->getComponentByType('GridFieldDataColumns');
$columns->setFieldFormatting(array(
'ParentTitle' => function($value, &$item) {
return sprintf(
'<a href="%s" class="cms-panel-link external-link action" target="_blank">%s</a>',
Convert::raw2xml($item->Link()),
Convert::raw2xml($value)
);
}
));
// Add bulk option
$manager = new GridFieldBulkManager();
$manager->addBulkAction(
'markAsSpam', 'Mark as spam', 'CommentsGridFieldBulkAction_MarkAsSpam',
array(
'isAjax' => true,
'icon' => 'delete',
'isDestructive' => true
)
);
$this->addComponent($manager);
}
}

View File

@ -17,48 +17,122 @@ class CommentingController extends Controller {
'doPreviewComment' 'doPreviewComment'
); );
/**
* Base class this commenting form is for
*
* @var string
*/
private $baseClass = ""; private $baseClass = "";
private $ownerRecord = "";
private $ownerController = ""; /**
* The record this commenting form is for
*
* @var DataObject
*/
private $ownerRecord = null;
/**
* Parent controller record
*
* @var Controller
*/
private $ownerController = null;
/**
* Backup url to return to
*
* @var string
*/
protected $fallbackReturnURL = null; protected $fallbackReturnURL = null;
/**
* Set the base class to use
*
* @param string $class
*/
public function setBaseClass($class) { public function setBaseClass($class) {
$this->baseClass = $class; $this->baseClass = $class;
} }
/**
* Get the base class used
*
* @return string
*/
public function getBaseClass() { public function getBaseClass() {
return $this->baseClass; return $this->baseClass;
} }
/**
* Set the record this controller is working on
*
* @param DataObject $record
*/
public function setOwnerRecord($record) { public function setOwnerRecord($record) {
$this->ownerRecord = $record; $this->ownerRecord = $record;
} }
/**
* Get the record
*
* @return DataObject
*/
public function getOwnerRecord() { public function getOwnerRecord() {
return $this->ownerRecord; return $this->ownerRecord;
} }
/**
* Set the parent controller
*
* @param Controller $controller
*/
public function setOwnerController($controller) { public function setOwnerController($controller) {
$this->ownerController = $controller; $this->ownerController = $controller;
} }
/**
* Get the parent controller
*
* @return Controller
*/
public function getOwnerController() { public function getOwnerController() {
return $this->ownerController; return $this->ownerController;
} }
/**
* Get the commenting option for the current state
*
* @param string $key
* @return mixed Result if the setting is available, or null otherwise
*/
public function getOption($key) {
// If possible use the current record
if($record = $this->getOwnerRecord()) {
return $record->getCommentsOption($key);
}
// Otherwise a singleton of that record
if($class = $this->getBaseClass()) {
return singleton($class)->getCommentsOption($key);
}
// Otherwise just use the default options
return singleton('CommentsExtension')->getCommentsOption($key);
}
/** /**
* Workaround for generating the link to this controller * Workaround for generating the link to this controller
* *
* @return string * @return string
*/ */
public function Link($action = "", $id = '', $other = '') { public function Link($action = '', $id = '', $other = '') {
return Controller::join_links(__CLASS__ , $action, $id, $other); return Controller::join_links(Director::baseURL(), __CLASS__ , $action, $id, $other);
} }
/** /**
* Outputs the RSS feed of comments * Outputs the RSS feed of comments
* *
* @return XML * @return HTMLText
*/ */
public function rss() { public function rss() {
return $this->getFeed($this->request)->outputToBrowser(); return $this->getFeed($this->request)->outputToBrowser();
@ -81,42 +155,37 @@ class CommentingController extends Controller {
$class = $request->param('ID'); $class = $request->param('ID');
$id = $request->param('OtherID'); $id = $request->param('OtherID');
// Support old pageid param
if(!$id && !$class && ($id = $request->getVar('pageid'))) {
$class = 'SiteTree';
}
$comments = Comment::get()->filter(array( $comments = Comment::get()->filter(array(
'Moderated' => 1, 'Moderated' => 1,
'IsSpam' => 0, 'IsSpam' => 0,
)); ));
if($request->getVar('pageid')) { // Check if class filter
$comments = $comments->filter(array( if($class) {
'BaseClass' => 'SiteTree', if(!is_subclass_of($class, 'DataObject') || !$class::has_extension('CommentsExtension')) {
'ParentID' => $request->getVar('pageid'),
));
$link = $this->Link('rss', 'SiteTree', $id);
} elseif($class && $id) {
if(Commenting::has_commenting($class)) {
$comments = $comments->filter(array(
'BaseClass' => $class,
'ParentID' => $id,
));
$link = $this->Link('rss', Convert::raw2xml($class), (int) $id);
} else {
return $this->httpError(404); return $this->httpError(404);
} }
} elseif($class) { $this->setBaseClass($class);
if(Commenting::has_commenting($class)) { $comments = $comments->filter('BaseClass', $class);
$comments = $comments->filter('BaseClass', $class); $link = Controller::join_links($link, $class);
} else {
return $this->httpError(404); // Check if id filter
if($id) {
$comments = $comments->filter('ParentID', $id);
$link = Controller::join_links($link, $id);
$this->setOwnerRecord(DataObject::get_by_id($class, $id));
} }
} }
$title = _t('CommentingController.RSSTITLE', "Comments RSS Feed"); $title = _t('CommentingController.RSSTITLE', "Comments RSS Feed");
$comments = new PaginatedList($comments, $request); $comments = new PaginatedList($comments, $request);
$comments->setPageLength(Commenting::get_config_value(null, 'comments_per_page')); $comments->setPageLength($this->getOption('comments_per_page'));
return new RSSFeed( return new RSSFeed(
$comments, $comments,
@ -232,8 +301,7 @@ class CommentingController extends Controller {
* @return Form * @return Form
*/ */
public function CommentsForm() { public function CommentsForm() {
$usePreview = Commenting::get_config_value($this->getBaseClass(), 'use_preview'); $usePreview = $this->getOption('use_preview');
$member = Member::currentUser();
$fields = new FieldList( $fields = new FieldList(
$dataFields = new CompositeField( $dataFields = new CompositeField(
@ -292,34 +360,30 @@ 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()) {
$require_login = Commenting::get_config_value($this->getBaseClass(), 'require_login');
$permission = Commenting::get_config_value($this->getBaseClass(), 'required_permission');
if(($require_login || $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));
$form->setFields($fields);
}
// 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.
$url = (isset($_SERVER['REQUEST_URI'])) ? Director::protocolAndHost() . '' . $_SERVER['REQUEST_URI'] : false;
$form->loadDataFrom(array( $form->loadDataFrom(array(
'ParentID' => $record->ID, 'ParentID' => $record->ID,
'ReturnURL' => $url, 'ReturnURL' => $this->request->getURL(),
'BaseClass' => $this->getBaseClass() 'BaseClass' => $this->getBaseClass()
)); ));
} }
// Set it so the user gets redirected back down to the form upon form fail // Set it so the user gets redirected back down to the form upon form fail
$form->setRedirectToFormOnValidationError(true); $form->setRedirectToFormOnValidationError(true);
@ -336,7 +400,7 @@ class CommentingController extends Controller {
// allow previous value to fill if comment not stored in cookie (i.e. validation error) // allow previous value to fill if comment not stored in cookie (i.e. validation error)
$prevComment = Cookie::get('CommentsForm_Comment'); $prevComment = Cookie::get('CommentsForm_Comment');
if($prevComment && $prevComment != ''){ if($prevComment && $prevComment != ''){
$form->loadDataFrom(array("Comment" => $prevComment)); $form->loadDataFrom(array("Comment" => $prevComment));
} }
} }
@ -357,13 +421,14 @@ class CommentingController extends Controller {
* @param Form $form * @param Form $form
*/ */
public function doPostComment($data, $form) { public function doPostComment($data, $form) {
$class = (isset($data['BaseClass'])) ? $data['BaseClass'] : $this->getBaseClass(); // Load class and parent from data
$usePreview = Commenting::get_config_value($class, 'use_preview'); if(isset($data['BaseClass'])) {
$isPreview = ($usePreview && isset($data['IsPreview']) && $data['IsPreview']); $this->setBaseClass($data['BaseClass']);
}
// if no class then we cannot work out what controller or model they if(isset($data['ParentID']) && ($class = $this->getBaseClass())) {
// are on so throw an error $this->setOwnerRecord($class::get()->byID($data['ParentID']));
if(!$class) user_error("No OwnerClass set on CommentingController.", E_USER_ERROR); }
if(!$this->getOwnerRecord()) return $this->httpError(404);
// cache users data // cache users data
Cookie::set("CommentsForm_UserData", Convert::raw2json($data)); Cookie::set("CommentsForm_UserData", Convert::raw2json($data));
@ -373,7 +438,7 @@ class CommentingController extends Controller {
$this->extend('onBeforePostComment', $form); $this->extend('onBeforePostComment', $form);
// If commenting can only be done by logged in users, make sure the user is logged in // If commenting can only be done by logged in users, make sure the user is logged in
if(!Commenting::can_member_post($class)) { if(!$this->getOwnerRecord()->canPostComment()) {
return Security::permissionFailure( return Security::permissionFailure(
$this, $this,
_t( _t(
@ -389,9 +454,9 @@ class CommentingController extends Controller {
} }
// is moderation turned on // is moderation turned on
$requireModeration = Commenting::get_config_value($class, 'require_moderation'); $requireModeration = $this->getOption('require_moderation');
if(!$requireModeration){ if(!$requireModeration) {
$requireModerationNonmembers = Commenting::get_config_value($class, 'require_moderation_nonmembers'); $requireModerationNonmembers = $this->getOption('require_moderation_nonmembers');
$requireModeration = $requireModerationNonmembers ? !Member::currentUser() : false; $requireModeration = $requireModerationNonmembers ? !Member::currentUser() : false;
} }
@ -400,18 +465,19 @@ class CommentingController extends Controller {
Session::set('CommentsModerated', 1); Session::set('CommentsModerated', 1);
} }
$comment = new Comment(); $comment = new Comment();
$form->saveInto($comment); $form->saveInto($comment);
$comment->AllowHtml = Commenting::get_config_value($class, 'html_allowed'); $comment->AllowHtml = $this->getOption('html_allowed');
$comment->Moderated = !$requireModeration; $comment->Moderated = !$requireModeration;
// Save into DB, or call pre-save hooks to give accurate preview // Save into DB, or call pre-save hooks to give accurate preview
$usePreview = $this->getOption('use_preview');
$isPreview = $usePreview && !empty($data['IsPreview']);
if($isPreview) { if($isPreview) {
$comment->extend('onBeforeWrite', $dummy); $comment->extend('onBeforeWrite');
} else { } else {
$comment->write(); $comment->write();
// extend hook to allow extensions. Also see onBeforePostComment // extend hook to allow extensions. Also see onBeforePostComment
$this->extend('onAfterPostComment', $comment); $this->extend('onAfterPostComment', $comment);
@ -432,7 +498,7 @@ class CommentingController extends Controller {
// Given a redirect page exists, attempt to link to the correct anchor // Given a redirect page exists, attempt to link to the correct anchor
if(!$comment->Moderated) { if(!$comment->Moderated) {
// Display the "awaiting moderation" text // Display the "awaiting moderation" text
$holder = Commenting::get_config_value($comment->BaseClass, 'comments_holder_id'); $holder = $this->getOption('comments_holder_id');
$hash = "{$holder}_PostCommentForm_error"; $hash = "{$holder}_PostCommentForm_error";
} elseif($comment->IsSpam) { } elseif($comment->IsSpam) {
// Link to the form with the error message contained // Link to the form with the error message contained

View File

@ -13,40 +13,40 @@
* @property integer $ParentID ID of the parent page / dataobject * @property integer $ParentID ID of the parent page / dataobject
* @property boolean $AllowHtml If true, treat $Comment as HTML instead of plain text * @property boolean $AllowHtml If true, treat $Comment as HTML instead of plain text
* @property string $SecretToken Secret admin token required to provide moderation links between sessions * @property string $SecretToken Secret admin token required to provide moderation links between sessions
* @method Member Author() * @method HasManyList ChildComments() List of child comments
* @method Member Author() Member object who created this comment
* @package comments * @package comments
*/ */
class Comment extends DataObject { class Comment extends DataObject {
private static $db = array( private static $db = array(
"Name" => "Varchar(200)", "Name" => "Varchar(200)",
"Comment" => "Text", "Comment" => "Text",
"Email" => "Varchar(200)", "Email" => "Varchar(200)",
"URL" => "Varchar(255)", "URL" => "Varchar(255)",
"BaseClass" => "Varchar(200)", "BaseClass" => "Varchar(200)",
"Moderated" => "Boolean", "Moderated" => "Boolean(1)",
"IsSpam" => "Boolean", "IsSpam" => "Boolean(0)",
"ParentID" => "Int", "ParentID" => "Int",
'AllowHtml' => "Boolean", 'AllowHtml' => "Boolean",
"SecretToken" => "Varchar(255)", "SecretToken" => "Varchar(255)",
); );
private static $has_one = array( private static $has_one = array(
"Author" => "Member" "Author" => "Member",
); );
private static $default_sort = '"Created" DESC'; private static $default_sort = '"Created" DESC';
private static $has_many = array();
private static $many_many = array();
private static $defaults = array( private static $defaults = array(
"Moderated" => 1, "Moderated" => 1,
"IsSpam" => 0 "IsSpam" => 0,
); );
private static $casting = array( private static $casting = array(
'Title' => 'Varchar',
'ParentTitle' => 'Varchar',
'ParentClassName' => 'Varchar',
'AuthorName' => 'Varchar', 'AuthorName' => 'Varchar',
'RSSName' => 'Varchar', 'RSSName' => 'Varchar',
'DeleteLink' => 'Varchar', 'DeleteLink' => 'Varchar',
@ -70,7 +70,7 @@ class Comment extends DataObject {
'Comment' => 'Comment', 'Comment' => 'Comment',
'Created' => 'Date Posted', 'Created' => 'Date Posted',
'ParentTitle' => 'Parent', 'ParentTitle' => 'Parent',
'IsSpam' => 'Is Spam' 'IsSpam' => 'Is Spam',
); );
public function onBeforeWrite() { public function onBeforeWrite() {
@ -136,8 +136,7 @@ class Comment extends DataObject {
* @return string * @return string
*/ */
public function Permalink() { public function Permalink() {
$prefix = Commenting::get_config_value($this->BaseClass, 'comment_permalink_prefix'); $prefix = $this->getOption('comment_permalink_prefix');
return $prefix . $this->ID; return $prefix . $this->ID;
} }
@ -159,6 +158,26 @@ class Comment extends DataObject {
return $labels; return $labels;
} }
/**
* Get the commenting option
*
* @param string $key
* @return mixed Result if the setting is available, or null otherwise
*/
public function getOption($key) {
// If possible use the current record
$record = $this->getParent();
if(!$record && $this->BaseClass) {
// Otherwise a singleton of that record
$record = singleton($this->BaseClass);
} elseif(!$record) {
// Otherwise just use the default options
$record = singleton('CommentsExtension');
}
return $record->getCommentsOption($key);
}
/** /**
* Returns the parent {@link DataObject} this comment is attached too * Returns the parent {@link DataObject} this comment is attached too
@ -166,11 +185,9 @@ class Comment extends DataObject {
* @return DataObject * @return DataObject
*/ */
public function getParent() { public function getParent() {
if(!$this->BaseClass) { return $this->BaseClass && $this->ParentID
$this->BaseClass = "SiteTree"; ? DataObject::get_by_id($this->BaseClass, $this->ParentID, true)
} : null;
return ($this->ParentID) ? DataObject::get_by_id($this->BaseClass, $this->ParentID) : null;
} }
@ -180,43 +197,35 @@ class Comment extends DataObject {
* @return string * @return string
*/ */
public function getParentTitle() { public function getParentTitle() {
if($parent = $this->getParent()){ if($parent = $this->getParent()) {
return ($parent && $parent->Title) ? $parent->Title : $parent->ClassName . " #" . $parent->ID; return $parent->Title ?: ($parent->ClassName . " #" . $parent->ID);
} }
} }
/** /**
* Comment-parent classnames obviousely vary, return the parent classname * Comment-parent classnames obviously vary, return the parent classname
* *
* @return string * @return string
*/ */
public function getParentClassName() { public function getParentClassName() {
$default = 'SiteTree';
if(!$this->BaseClass) {
return $default;
}
return $this->BaseClass; return $this->BaseClass;
} }
public function castingHelper($field) {
// Safely escape the comment
if($field === 'EscapedComment') {
return $this->AllowHtml ? 'HTMLText' : 'Varchar';
}
return parent::castingHelper($field);
}
/** /**
* Return the content for this comment escaped depending on the Html state. * Content to be safely escaped on the frontend
* *
* @return HTMLText * @return string
*/ */
public function getEscapedComment() { public function getEscapedComment() {
$comment = $this->dbObject('Comment'); return $this->Comment;
if ($comment->exists()) {
if ($this->AllowHtml) {
return DBField::create_field('HTMLText', nl2br($comment->RAW()));
} else {
return DBField::create_field('HTMLText', sprintf("<p>%s</p>", nl2br($comment->XML())));
}
}
return $comment;
} }
/** /**
@ -225,7 +234,7 @@ class Comment extends DataObject {
* @return boolean * @return boolean
*/ */
public function isPreview() { public function isPreview() {
return ($this->ID < 1); return !$this->exists();
} }
/** /**
@ -250,11 +259,13 @@ class Comment extends DataObject {
// Standard mechanism for accepting permission changes from decorators // Standard mechanism for accepting permission changes from decorators
$extended = $this->extendedCan('canView', $member); $extended = $this->extendedCan('canView', $member);
if($extended !== null) return $extended; if($extended !== null) return $extended;
$page = $this->getParent();
$admin = (bool) Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin');
return (($page && $page->ProvideComments && $page->canView($member)) || $admin); // Allow admin
if(Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) return true;
// Check if parent has comments and can be viewed
$parent = $this->getParent();
return $parent && $parent->ProvideComments && $parent->canView($member);
} }
/** /**
@ -301,8 +312,8 @@ class Comment extends DataObject {
public function getAuthorName() { public function getAuthorName() {
if($this->Name) { if($this->Name) {
return $this->Name; return $this->Name;
} else if($this->Author()) { } else if($author = $this->Author()) {
return $this->Author()->getName(); return $author->getName();
} }
} }
@ -434,9 +445,7 @@ class Comment extends DataObject {
*/ */
public function getHtmlPurifierService() { public function getHtmlPurifierService() {
$config = HTMLPurifier_Config::createDefault(); $config = HTMLPurifier_Config::createDefault();
$config->set('HTML.AllowedElements', $config->set('HTML.AllowedElements', $this->getOption('html_allowed_elements'));
Commenting::get_config_value($this->BaseClass, 'html_allowed_elements')
);
$config->set('AutoFormat.AutoParagraph', true); $config->set('AutoFormat.AutoParagraph', true);
$config->set('AutoFormat.Linkify', true); $config->set('AutoFormat.Linkify', true);
$config->set('URI.DisableExternalResources', true); $config->set('URI.DisableExternalResources', true);
@ -444,17 +453,19 @@ class Comment extends DataObject {
return new HTMLPurifier($config); return new HTMLPurifier($config);
} }
/* /**
Calcualate the gravatar link from the email address * Calcualate the gravatar link from the email address
*/ *
* @return string
*/
public function Gravatar() { public function Gravatar() {
$gravatar = ''; $gravatar = '';
$use_gravatar = Commenting::get_config_value($this->BaseClass, 'use_gravatar'); $use_gravatar = $this->getOption('use_gravatar');
if ($use_gravatar) { if ($use_gravatar) {
$gravatar = "http://www.gravatar.com/avatar/" . md5( strtolower(trim($this->Email))); $gravatar = "http://www.gravatar.com/avatar/" . md5( strtolower(trim($this->Email)));
$gravatarsize = Commenting::get_config_value($this->BaseClass, 'gravatar_size'); $gravatarsize = $this->getOption('gravatar_size');
$gravatardefault = Commenting::get_config_value($this->BaseClass, 'gravatar_default'); $gravatardefault = $this->getOption('gravatar_default');
$gravatarrating = Commenting::get_config_value($this->BaseClass, 'gravatar_rating'); $gravatarrating = $this->getOption('gravatar_rating');
$gravatar.= "?s=".$gravatarsize."&d=".$gravatardefault."&r=".$gravatarrating; $gravatar.= "?s=".$gravatarsize."&d=".$gravatardefault."&r=".$gravatarrating;
} }

View File

@ -7,10 +7,37 @@
*/ */
class CommentsExtension extends DataExtension { class CommentsExtension extends DataExtension {
/**
* Default configuration values
*
* @var array
* @config
*/
private static $comments = array(
'enabled' => true, // Allows commenting to be disabled even if the extension is present
'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
'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.
'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) { public static function get_extra_config($class, $extension, $args = null) {
$config = array(); $config = array();
// if it is attached to the SiteTree then we need to add ProvideComments // if it is attached to the SiteTree then we need to add ProvideComments
if(is_subclass_of($class, 'SiteTree') || $class == 'SiteTree') { if(is_subclass_of($class, 'SiteTree') || $class == 'SiteTree') {
$config['db'] = array('ProvideComments' => 'Boolean'); $config['db'] = array('ProvideComments' => 'Boolean');
@ -31,53 +58,188 @@ class CommentsExtension extends DataExtension {
*/ */
public function updateSettingsFields(FieldList $fields) { public function updateSettingsFields(FieldList $fields) {
if($this->attachedToSiteTree()) { if($this->attachedToSiteTree()) {
$fields->addFieldToTab('Root.Settings', $fields->addFieldToTab('Root.Settings',
new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments')) new CheckboxField('ProvideComments', _t('Comment.ALLOWCOMMENTS', 'Allow Comments'))
); );
} }
} }
/**
* Returns the RelationList of all comments against this object. Can be used as a data source
* for a gridfield with write access.
*
* @return CommentList
*/
public function AllComments() {
$comments = CommentList::create($this->ownerBaseClass)->forForeignID($this->owner->ID);
$this->owner->extend('updateAllComments', $comments);
return $comments;
}
public function getComments() {
Deprecation::notice('2.0', 'Use PagedComments to get paged coments');
return $this->PagedComments();
}
/** /**
* Returns a list of all the comments attached to this record. * Returns the root level comments, with spam and unmoderated items excluded, for use in the frontend
*
* @return CommentList
*/
public function Comments() {
// Get all non-spam comments
$order = $this->owner->getCommentsOption('order_comments_by');
$list = $this
->AllComments()
->sort($order)
->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')
) {
$list = $list->filter('Moderated', 1);
}
$this->owner->extend('updateComments', $list);
return $list;
}
/**
* Returns a paged list of the root level comments, with spam and unmoderated items excluded,
* for use in the frontend
* *
* @return PaginatedList * @return PaginatedList
*/ */
public function getComments() { public function PagedComments() {
$order = Commenting::get_config_value($this->ownerBaseClass, 'order_comments_by'); $list = $this->Comments();
$list = Comment::get()->filter(array( // Add pagination
'ParentID' => $this->owner->ID, $list = new PaginatedList($list, Controller::curr()->getRequest());
'BaseClass' => $this->ownerBaseClass $list->setPaginationGetVar('commentsstart'.$this->owner->ID);
))->sort($order); $list->setPageLength($this->owner->getCommentsOption('comments_per_page'));
// Filter content for unauthorised users
if (!($member = Member::currentUser()) || !Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
// Filter unmoderated comments for non-administrators if moderation is enabled
if (Commenting::get_config_value($this->ownerBaseClass, 'require_moderation') || Commenting::get_config_value($this->ownerBaseClass, 'require_moderation_nonmembers')) {
$list = $list->filter(array(
'Moderated' => 1,
'IsSpam' => 0
));
} else {
// Filter spam comments for non-administrators if auto-moderated
$list = $list->filter('IsSpam', 0);
}
}
$list = new PaginatedList($list);
$list->setPageLength(Commenting::get_config_value(
$this->ownerBaseClass, 'comments_per_page'
));
$controller = Controller::curr();
$list->setPageStart($controller->request->getVar("commentsstart". $this->owner->ID));
$list->setPaginationGetVar("commentsstart". $this->owner->ID);
$this->owner->extend('updatePagedComments', $list);
return $list; return $list;
} }
/**
* Check if comments are configured for this page even if they are currently disabled.
* Do not include the comments on pages which don't have id's such as security pages
*
* @deprecated since version 2.0
*
* @return boolean
*/
public function getCommentsConfigured() {
Deprecation::notice('2.0', 'getCommentsConfigured is deprecated. Use getCommentsEnabled instead');
return true; // by virtue of all classes with this extension being 'configured'
}
/**
* Determine if comments are enabled for this instance
*
* @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;
}
/**
* Get the HTML ID for the comment holder in the template
*
* @return string
*/
public function getCommentHolderID() {
return $this->owner->getCommentsOption('comments_holder_id');
}
/**
* @deprecated since version 2.0
*/
public function getPostingRequiresPermission() {
Deprecation::notice('2.0', 'Use getPostingRequiredPermission instead');
return $this->getPostingRequiredPermission();
}
/**
* Permission codes required in order to post (or empty if none required)
*
* @return string|array Permission or list of permissions, if required
*/
public function getPostingRequiredPermission() {
return $this->owner->getCommentsOption('required_permission');
}
public function canPost() {
Deprecation::notice('2.0', 'Use canPostComment instead');
return $this->canPostComment();
}
/**
* Determine if a user can post comments on this item
*
* @param Member $member Member to check
* @return boolean
*/
public function canPostComment($member = null) {
// Check if member is required
$requireLogin = $this->owner->getCommentsOption('require_login');
if(!$requireLogin) return true;
// Check member is logged in
$member = $member ?: Member::currentUser();
if(!$member) return false;
// If member required check permissions
$requiredPermission = $this->getPostingRequiredPermission();
if($requiredPermission && !Permission::checkMember($member, $requiredPermission)) return false;
return true;
}
/**
* Determine if this member can moderate comments in the CMS
*
* @param Member $member
* @return boolean
*/
public function canModerateComments($member = null) {
return $this->owner->canEdit($member);
}
public function getRssLink() {
Deprecation::notice('2.0', 'Use getCommentRSSLink instead');
return $this->getCommentRSSLink();
}
/**
* Gets the RSS link to all comments
*
* @return string
*/
public function getCommentRSSLink() {
return Controller::join_links(Director::baseURL(), "CommentingController/rss");
}
public function getRssLinkPage() {
Deprecation::notice('2.0', 'Use getCommentRSSLinkPage instead');
return $this->getCommentRSSLinkPage();
}
/**
* Get the RSS link to all comments on this page
*
* @return string
*/
public function getCommentRSSLinkPage() {
return Controller::join_links(
$this->getCommentRSSLink(), $this->ownerBaseClass, $this->owner->ID
);
}
/** /**
* Comments interface for the front end. Includes the CommentAddForm and the composition * Comments interface for the front end. Includes the CommentAddForm and the composition
@ -91,23 +253,15 @@ class CommentsExtension extends DataExtension {
* @see docs/en/Extending * @see docs/en/Extending
*/ */
public function CommentsForm() { public function CommentsForm() {
if(Commenting::has_commenting($this->ownerBaseClass) && Commenting::get_config_value($this->ownerBaseClass, 'include_js')) { // Check if enabled
$enabled = $this->getCommentsEnabled();
if($enabled && $this->owner->getCommentsOption('include_js')) {
Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js'); Requirements::javascript(THIRDPARTY_DIR . '/jquery/jquery.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-validate/lib/jquery.form.js'); Requirements::javascript(THIRDPARTY_DIR . '/jquery-validate/lib/jquery.form.js');
Requirements::javascript(THIRDPARTY_DIR . '/jquery-validate/jquery.validate.pack.js'); Requirements::javascript(THIRDPARTY_DIR . '/jquery-validate/jquery.validate.pack.js');
Requirements::javascript('comments/javascript/CommentsInterface.js'); Requirements::javascript('comments/javascript/CommentsInterface.js');
} }
$interface = new SSViewer('CommentsInterface');
// detect whether we comments are enabled. By default if $CommentsForm is included
// on a {@link DataObject} then it is enabled, however {@link SiteTree} objects can
// trigger comments on / off via ProvideComments
$enabled = (!$this->attachedToSiteTree() || $this->owner->ProvideComments) ? true : false;
// do not include the comments on pages which don't have id's such as security pages
if($this->owner->ID < 0) return false;
$controller = CommentingController::create(); $controller = CommentingController::create();
$controller->setOwnerRecord($this->owner); $controller->setOwnerRecord($this->owner);
$controller->setBaseClass($this->ownerBaseClass); $controller->setBaseClass($this->ownerBaseClass);
@ -120,18 +274,13 @@ class CommentsExtension extends DataExtension {
// a little bit all over the show but to ensure a slightly easier upgrade for users // a little bit all over the show but to ensure a slightly easier upgrade for users
// return back the same variables as previously done in comments // return back the same variables as previously done in comments
return $interface->process(new ArrayData(array( return $this
'CommentHolderID' => Commenting::get_config_value($this->ownerBaseClass, 'comments_holder_id'), ->owner
'PostingRequiresPermission' => Commenting::get_config_value($this->ownerBaseClass, 'required_permission'), ->customise(array(
'CanPost' => Commenting::can_member_post($this->ownerBaseClass), 'AddCommentForm' => $form,
'RssLink' => "CommentingController/rss", 'ModeratedSubmitted' => $moderatedSubmitted,
'RssLinkPage' => "CommentingController/rss/". $this->ownerBaseClass . '/'.$this->owner->ID, ))
'CommentsEnabled' => $enabled, ->renderWith('CommentsInterface');
'Parent' => $this->owner,
'AddCommentForm' => $form,
'ModeratedSubmitted' => $moderatedSubmitted,
'Comments' => $this->getComments()
)));
} }
/** /**
@ -144,14 +293,71 @@ class CommentsExtension extends DataExtension {
return (is_subclass_of($class, 'SiteTree')) || ($class == 'SiteTree'); return (is_subclass_of($class, 'SiteTree')) || ($class == 'SiteTree');
} }
/** /**
* @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()} * @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()}
*/ */
public function PageComments() { public function PageComments() {
// This method is very commonly used, don't throw a warning just yet // This method is very commonly used, don't throw a warning just yet
//user_error('$PageComments is deprecated. Please use $CommentsForm', E_USER_WARNING); Deprecation::notice('1.0', '$PageComments is deprecated. Please use $CommentsForm');
return $this->CommentsForm(); return $this->CommentsForm();
} }
/**
* Get the commenting option for this object
*
* This can be overridden in any instance or extension to customise the option available
*
* @param string $key
* @return mixed Result if the setting is available, or null otherwise
*/
public function getCommentsOption($key) {
$settings = $this->owner // In case singleton is called on the extension directly
? $this->owner->config()->comments
: Config::inst()->get(__CLASS__, 'comments');
$value = null;
if(isset($settings[$key])) $value = $settings[$key];
// To allow other extensions to customise this option
if($this->owner) $this->owner->extend('updateCommentsOption', $key, $value);
return $value;
}
public function updateCMSFields(\FieldList $fields) {
// Disable moderation if not permitted
if(!$this->owner->canModerateComments()) return;
// Create gridfield config
$commentsConfig = CommentsGridFieldConfig::create();
$needs = new GridField(
'CommentsNeedsModeration',
_t('CommentsAdmin.NeedsModeration', 'Needs Moderation'),
$this->owner->AllComments()->filter('Moderated', 0),
$commentsConfig
);
$moderated = new GridField(
'CommentsModerated',
_t('CommentsAdmin.Moderated', 'Moderated'),
$this->owner->AllComments()->filter('Moderated', 1),
$commentsConfig
);
if($fields->hasTabSet()) {
$tabset = new TabSet(
'Comments',
new Tab('CommentsNeedsModerationTab', _t('CommentAdmin.NeedsModeration', 'Needs Moderation'),
$needs
),
new Tab('CommentsModeratedTab', _t('CommentAdmin.Moderated', 'Moderated'),
$moderated
)
);
$fields->addFieldToTab('Root', $tabset);
} else {
$fields->push($needs);
$fields->push($moderated);
}
}
} }

View File

@ -19,7 +19,7 @@
}, },
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "1.2.x-dev" "dev-master": "2.0.x-dev"
} }
} }
} }

View File

@ -5,33 +5,62 @@
The module provides a number of built in configuration settings below are the The module provides a number of built in configuration settings below are the
default settings default settings
// mysite/_config.php In order to add commenting to your site, the minimum amount of work necessary is to add the `CommentsExtension` to
the base class for the object which holds comments.
Commenting::add('Foo', array(
'require_login' => false, // boolean, whether a user needs to login ```yaml
'required_permission' => false, // required permission to comment (or array of permissions) SiteTree:
'include_js' => true, // Enhance operation by ajax behaviour on moderation links extensions:
'show_comments_when_disabled' => false, // when comments are disabled should we show older comments (if available) - CommentsExtension
'order_comments_by' => "\"Created\" DESC", ```
'comments_per_page' => 10,
'comments_holder_id' => "comments-holder", // id for the comments holder ## Configuration
'comment_permalink_prefix' => "comment-", // id prefix for each comment. If needed make this different
'require_moderation' => false, In order to configure options for any class you should assign the specific option a value under the 'comments'
'html_allowed' => false, // allow for sanitized HTML in comments config of the specified class.
'html_allowed_elements' => array('a', 'img', 'i', 'b'),
'use_preview' => false, // preview formatted comment (when allowing HTML). Requires include_js=true ```yaml
'use_gravatar' => false, SiteTree:
'gravatar_size' => 80 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
```
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`
// mysite/_config.php - Sets require_login to true for all pages ```yaml
Commenting::set_config_value('SiteTree', 'require_login', true); # Set the default option for pages to require login
SiteTree:
// mysite/_config.php - Returns the setting comments:
Commenting::get_config_value('SiteTree', 'require_login'); require_login: true
```
```php
// Get the setting
$loginRequired = singleton('SiteTree')->getCommentsOption('require_login');
```
## HTML Comments ## HTML Comments
@ -56,12 +85,20 @@ properly sanitized. Don't allow tags like `<script>` or arbitrary attributes.
Gravatars can be turned on by adding this to your mysite/_config.php file Gravatars can be turned on by adding this to your mysite/_config.php file
Commenting::set_config_value('SiteTree', 'use_gravatar', true); ```yaml
SiteTree:
comments:
use_gravitar: true
````
The default size is 80 pixels, as per the gravatar site if the 's' parameter is The default size is 80 pixels, as per the gravatar site if the 's' parameter is
omitted. To change this add the following (again to mysite/_config.php): omitted. To change this add the following (again to mysite/_config.php):
Commenting::set_config_value('SiteTree', 'gravatar_size', 40); ```yaml
SiteTree:
comments:
gravatar_size: 40
```
If the email address used to comment does not have a gravatar, it is possible If the email address used to comment does not have a gravatar, it is possible
to configure the default image shown. Valid values can be found at to configure the default image shown. Valid values can be found at
@ -79,14 +116,22 @@ return an HTTP 404 (File Not Found) response.
* blank: a transparent PNG image (border added to HTML below for demonstration * blank: a transparent PNG image (border added to HTML below for demonstration
purposes) purposes)
To change the default image style, add the following to mysite/_config.php To change the default image style, add the following to mysite/_config/config.yml
Commenting::set_config_value('SiteTree', 'gravatar_default', 'retro'); ```yaml
SiteTree:
comments:
gravatar_default: 'retro'
```
The rating of the image can be changed by adding a line similar to this to The rating of the image can be changed by adding a line similar to this to
mysite/_config.php mysite/_config/config.yml
Commenting::set_config_value('SiteTree', 'gravatar_rating', 'r'); ```yaml
SiteTree:
comments:
gravatar_rating: 'r'
```
Vald values for rating are as follows: Vald values for rating are as follows:

View File

@ -3,13 +3,13 @@
<h4><% _t('CommentsInterface_ss.POSTCOM','Post your comment') %></h4> <h4><% _t('CommentsInterface_ss.POSTCOM','Post your comment') %></h4>
<% if $AddCommentForm %> <% if $AddCommentForm %>
<% if $CanPost %> <% if $canPostComment %>
<% if $ModeratedSubmitted %> <% if $ModeratedSubmitted %>
<p id="{$CommentHolderID}_PostCommentForm_error" class="message good"><% _t('CommentsInterface_ss.AWAITINGMODERATION', 'Your comment has been submitted and is now awaiting moderation.') %></p> <p id="{$CommentHolderID}_PostCommentForm_error" class="message good"><% _t('CommentsInterface_ss.AWAITINGMODERATION', 'Your comment has been submitted and is now awaiting moderation.') %></p>
<% end_if %> <% end_if %>
$AddCommentForm $AddCommentForm
<% else %> <% else %>
<p><% _t('CommentsInterface_ss.COMMENTLOGINERROR', 'You cannot post comments until you have logged in') %><% if PostingRequiresPermission %>,<% _t('CommentsInterface_ss.COMMENTPERMISSIONERROR', 'and that you have an appropriate permission level') %><% end_if %>. <p><% _t('CommentsInterface_ss.COMMENTLOGINERROR', 'You cannot post comments until you have logged in') %><% if $PostingRequiredPermission %>,<% _t('CommentsInterface_ss.COMMENTPERMISSIONERROR', 'and that you have an appropriate permission level') %><% end_if %>.
<a href="Security/login?BackURL={$Parent.Link}" title="<% _t('CommentsInterface_ss.LOGINTOPOSTCOMMENT', 'Login to post a comment') %>"><% _t('CommentsInterface_ss.COMMENTPOSTLOGIN', 'Login Here') %></a>. <a href="Security/login?BackURL={$Parent.Link}" title="<% _t('CommentsInterface_ss.LOGINTOPOSTCOMMENT', 'Login to post a comment') %>"><% _t('CommentsInterface_ss.COMMENTPOSTLOGIN', 'Login Here') %></a>.
</p> </p>
<% end_if %> <% end_if %>
@ -28,30 +28,9 @@
</li> </li>
<% end_loop %> <% end_loop %>
</ul> </ul>
<% with $Comments %>
<% if $Comments.MoreThanOnePage %> <% include CommentPagination %>
<div class="comments-pagination"> <% end_with %>
<p>
<% if $Comments.PrevLink %>
<a href="$Comments.PrevLink" class="previous">&laquo; <% _t('CommentsInterface_ss.PREV','previous') %></a>
<% end_if %>
<% if $Comments.Pages %>
<% loop $Comments.Pages %>
<% if $CurrentBool %>
<strong>$PageNum</strong>
<% else %>
<a href="$Link">$PageNum</a>
<% end_if %>
<% end_loop %>
<% end_if %>
<% if $Comments.NextLink %>
<a href="$Comments.NextLink" class="next"><% _t('CommentsInterface_ss.NEXT','next') %> &raquo;</a>
<% end_if %>
</p>
</div>
<% end_if %>
<% end_if %> <% end_if %>
<p class="no-comments-yet"<% if $Comments.Count %> style='display: none' <% end_if %> ><% _t('CommentsInterface_ss.NOCOMMENTSYET','No one has commented on this page yet.') %></p> <p class="no-comments-yet"<% if $Comments.Count %> style='display: none' <% end_if %> ><% _t('CommentsInterface_ss.NOCOMMENTSYET','No one has commented on this page yet.') %></p>
@ -65,9 +44,8 @@
<% end_if %> <% end_if %>
<p class="commenting-rss-feed"> <p class="commenting-rss-feed">
<a href="$RssLinkPage"><% _t('CommentsInterface_ss.RSSFEEDCOMMENTS', 'RSS feed for comments on this page') %></a> | <a href="$CommentRSSLinkPage"><% _t('CommentsInterface_ss.RSSFEEDCOMMENTS', 'RSS feed for comments on this page') %></a> |
<a href="$RssLink"><% _t('CommentsInterface_ss.RSSFEEDALLCOMMENTS', 'RSS feed for all comments') %></a> <a href="$CommentRSSLink"><% _t('CommentsInterface_ss.RSSFEEDALLCOMMENTS', 'RSS feed for all comments') %></a>
</p> </p>
</div> </div>
<% end_if %> <% end_if %>

View File

@ -1,3 +1,3 @@
<div class="pending-comment"> <div class="pending-comment">
<p><% _t('CommentsInterface_pendingcomment_ss.AWAITINGMODERATION', 'Your comment has been submitted and is now awaiting moderation.') %></p> <p><% _t('CommentsInterface_pendingcomment_ss.AWAITINGMODERATION', 'Your comment has been submitted and is now awaiting moderation.') %></p>
</div> </div>

View File

@ -0,0 +1,21 @@
<% if $MoreThanOnePage %>
<div class="comments-pagination">
<p>
<% if $PrevLink %>
<a href="$PrevLink.ATT" class="previous">&laquo; <% _t('CommentsInterface_ss.PREV','previous') %></a>
<% end_if %>
<% if $Pages %><% loop $Pages %>
<% if $CurrentBool %>
<strong>$PageNum</strong>
<% else %>
<a href="$Link.ATT">$PageNum</a>
<% end_if %>
<% end_loop %><% end_if %>
<% if $NextLink %>
<a href="$NextLink.ATT" class="next"><% _t('CommentsInterface_ss.NEXT','next') %> &raquo;</a>
<% end_if %>
</p>
</div>
<% end_if %>

View File

@ -13,39 +13,42 @@ class CommentsTest extends FunctionalTest {
public function setUp() { public function setUp() {
parent::setUp(); parent::setUp();
Config::nest();
Commenting::add('CommentableItem'); }
public function tearDown() {
Config::unnest();
parent::tearDown();
} }
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
Commenting::set_config_value('CommentableItem','require_moderation', false); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => false));
$item = $this->objFromFixture('CommentableItem', 'spammed'); $item = $this->objFromFixture('CommentableItem', 'spammed');
$this->assertDOSEquals(array( $this->assertDOSEquals(array(
array('Name' => 'Comment 1'), array('Name' => 'Comment 1'),
array('Name' => 'Comment 3') array('Name' => 'Comment 3')
), $item->getComments(), '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.
Commenting::set_config_value('CommentableItem','require_moderation', true); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => true));
$this->assertDOSEquals(array( $this->assertDOSEquals(array(
array('Name' => 'Comment 3') array('Name' => 'Comment 3')
), $item->getComments(), 'Only 1 non spam, moderated post should be shown'); ), $item->Comments(), 'Only 1 non spam, moderated post should be shown');
// when logged in as an user with CMS_ACCESS_CommentAdmin rights they // As of 2.0, logging in with admin no longer grants special privileges to view frontend comments and should
// should see all the comments whether we have moderation on or not // be done via the CMS
$this->logInWithPermission('CMS_ACCESS_CommentAdmin'); $this->logInWithPermission('CMS_ACCESS_CommentAdmin');
Commenting::set_config_value('CommentableItem','require_moderation', true); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => true));
$this->assertEquals(4, $item->getComments()->Count()); $this->assertEquals(1, $item->Comments()->Count());
Commenting::set_config_value('CommentableItem','require_moderation', false); Config::inst()->update('CommentableItem', 'comments', array('require_moderation' => false));
$this->assertEquals(4, $item->getComments()->Count()); $this->assertEquals(2, $item->Comments()->Count());
} }
public function testCanView() { public function testCanView() {
@ -353,6 +356,10 @@ class CommentableItem extends DataObject implements TestOnly {
'Title' => 'Varchar' 'Title' => 'Varchar'
); );
private static $extensions = array(
'CommentsExtension'
);
public function RelativeLink() { public function RelativeLink() {
return "CommentableItem_Controller"; return "CommentableItem_Controller";
} }