2010-11-29 23:24:17 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Represents a single comment object.
|
2015-03-27 05:40:00 +01:00
|
|
|
*
|
2015-04-13 05:41:18 +02:00
|
|
|
* @property string $Name
|
|
|
|
* @property string $Comment
|
|
|
|
* @property string $Email
|
|
|
|
* @property string $URL
|
|
|
|
* @property string $BaseClass
|
2015-03-27 05:40:00 +01:00
|
|
|
* @property boolean $Moderated
|
2015-04-13 05:41:18 +02:00
|
|
|
* @property boolean $IsSpam True if the comment is known as spam
|
|
|
|
* @property integer $ParentID ID of the parent page / dataobject
|
|
|
|
* @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
|
2015-04-07 05:56:00 +02:00
|
|
|
* @property integer $Depth Depth of this comment in the nested chain
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-04-07 05:56:00 +02:00
|
|
|
* @method HasManyList ChildComments() List of child comments
|
|
|
|
* @method Member Author() Member object who created this comment
|
2015-04-07 05:56:00 +02:00
|
|
|
* @method Comment ParentComment() Parent comment this is a reply to
|
2010-11-29 23:24:17 +01:00
|
|
|
* @package comments
|
|
|
|
*/
|
|
|
|
class Comment extends DataObject {
|
2015-04-07 05:56:00 +02:00
|
|
|
|
2015-04-13 05:41:18 +02:00
|
|
|
/**
|
|
|
|
* @var array
|
|
|
|
*/
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $db = array(
|
2015-04-13 05:41:18 +02:00
|
|
|
'Name' => 'Varchar(200)',
|
|
|
|
'Comment' => 'Text',
|
|
|
|
'Email' => 'Varchar(200)',
|
|
|
|
'URL' => 'Varchar(255)',
|
|
|
|
'BaseClass' => 'Varchar(200)',
|
|
|
|
'Moderated' => 'Boolean(0)',
|
|
|
|
'IsSpam' => 'Boolean(0)',
|
|
|
|
'ParentID' => 'Int',
|
|
|
|
'AllowHtml' => 'Boolean',
|
|
|
|
'SecretToken' => 'Varchar(255)',
|
2015-04-07 05:56:00 +02:00
|
|
|
'Depth' => 'Int',
|
2010-11-29 23:24:17 +01:00
|
|
|
);
|
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $has_one = array(
|
2015-04-07 05:56:00 +02:00
|
|
|
"Author" => "Member",
|
|
|
|
"ParentComment" => "Comment",
|
|
|
|
);
|
|
|
|
|
|
|
|
private static $has_many = array(
|
|
|
|
"ChildComments" => "Comment"
|
2010-11-29 23:24:17 +01:00
|
|
|
);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $default_sort = '"Created" DESC';
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $defaults = array(
|
2015-04-13 05:41:18 +02:00
|
|
|
'Moderated' => 0,
|
|
|
|
'IsSpam' => 0,
|
2010-12-11 05:26:14 +01:00
|
|
|
);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $casting = array(
|
2015-04-07 05:56:00 +02:00
|
|
|
'Title' => 'Varchar',
|
|
|
|
'ParentTitle' => 'Varchar',
|
|
|
|
'ParentClassName' => 'Varchar',
|
2012-07-22 03:30:33 +02:00
|
|
|
'AuthorName' => 'Varchar',
|
2015-03-27 05:40:00 +01:00
|
|
|
'RSSName' => 'Varchar',
|
|
|
|
'DeleteLink' => 'Varchar',
|
|
|
|
'SpamLink' => 'Varchar',
|
|
|
|
'HamLink' => 'Varchar',
|
|
|
|
'ApproveLink' => 'Varchar',
|
|
|
|
'Permalink' => 'Varchar',
|
2010-11-29 23:24:17 +01:00
|
|
|
);
|
2012-06-01 08:34:31 +02:00
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $searchable_fields = array(
|
2012-06-01 08:34:31 +02:00
|
|
|
'Name',
|
|
|
|
'Email',
|
|
|
|
'Comment',
|
|
|
|
'Created',
|
|
|
|
'BaseClass',
|
|
|
|
);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2013-04-11 14:23:03 +02:00
|
|
|
private static $summary_fields = array(
|
2012-06-01 08:34:31 +02:00
|
|
|
'Name' => 'Submitted By',
|
|
|
|
'Email' => 'Email',
|
2015-04-07 05:56:00 +02:00
|
|
|
'Comment.LimitWordCount' => 'Comment',
|
2012-06-01 08:34:31 +02:00
|
|
|
'Created' => 'Date Posted',
|
2015-04-13 05:41:18 +02:00
|
|
|
'ParentTitle' => 'Post',
|
2015-04-07 05:56:00 +02:00
|
|
|
'IsSpam' => 'Is Spam',
|
2012-06-01 08:34:31 +02:00
|
|
|
);
|
|
|
|
|
2015-04-07 05:56:00 +02:00
|
|
|
private static $field_labels = array(
|
|
|
|
'Author' => 'Author Member',
|
|
|
|
);
|
|
|
|
|
2013-02-21 16:39:57 +01:00
|
|
|
public function onBeforeWrite() {
|
|
|
|
parent::onBeforeWrite();
|
2012-06-01 08:34:31 +02:00
|
|
|
|
2013-02-21 16:39:57 +01:00
|
|
|
// Sanitize HTML, because its expected to be passed to the template unescaped later
|
2013-03-05 10:01:42 +01:00
|
|
|
if($this->AllowHtml) {
|
2013-02-21 16:39:57 +01:00
|
|
|
$this->Comment = $this->purifyHtml($this->Comment);
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
|
|
|
// Check comment depth
|
|
|
|
$this->updateDepth();
|
|
|
|
}
|
|
|
|
|
|
|
|
public function onBeforeDelete() {
|
|
|
|
parent::onBeforeDelete();
|
|
|
|
|
|
|
|
// Delete all children
|
|
|
|
foreach($this->ChildComments() as $comment) {
|
|
|
|
$comment->delete();
|
|
|
|
}
|
2013-02-21 16:39:57 +01:00
|
|
|
}
|
2015-03-27 05:40:00 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @return Comment_SecurityToken
|
|
|
|
*/
|
|
|
|
public function getSecurityToken() {
|
|
|
|
return Injector::inst()->createWithArgs('Comment_SecurityToken', array($this));
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-11 05:26:14 +01:00
|
|
|
/**
|
|
|
|
* Migrates the old {@link PageComment} objects to {@link Comment}
|
|
|
|
*/
|
|
|
|
public function requireDefaultRecords() {
|
|
|
|
parent::requireDefaultRecords();
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-11 05:26:14 +01:00
|
|
|
if(DB::getConn()->hasTable('PageComment')) {
|
2015-04-13 05:41:18 +02:00
|
|
|
$comments = DB::query('SELECT * FROM "PageComment"');
|
|
|
|
|
2010-12-11 05:26:14 +01:00
|
|
|
if($comments) {
|
2010-12-14 01:26:29 +01:00
|
|
|
while($pageComment = $comments->nextRecord()) {
|
2010-12-11 05:26:14 +01:00
|
|
|
// create a new comment from the older page comment
|
2012-03-17 00:21:58 +01:00
|
|
|
$comment = new Comment();
|
|
|
|
$comment->update($pageComment);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-11 05:26:14 +01:00
|
|
|
// set the variables which have changed
|
|
|
|
$comment->BaseClass = 'SiteTree';
|
2015-04-13 05:41:18 +02:00
|
|
|
$comment->URL = (isset($pageComment['CommenterURL'])) ? $pageComment['CommenterURL'] : '';
|
|
|
|
if((int) $pageComment['NeedsModeration'] == 0) $comment->Moderated = true;
|
|
|
|
|
2010-12-11 05:26:14 +01:00
|
|
|
$comment->write();
|
|
|
|
}
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
|
|
|
DB::alteration_message('Migrated PageComment to Comment', 'changed');
|
2010-12-11 05:26:14 +01:00
|
|
|
DB::getConn()->dontRequireTable('PageComment');
|
|
|
|
}
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
|
|
|
* Return a link to this comment
|
2010-11-30 10:49:21 +01:00
|
|
|
*
|
2015-04-13 05:41:18 +02:00
|
|
|
* @param string $action
|
|
|
|
*
|
2010-11-29 23:24:17 +01:00
|
|
|
* @return string link to this comment.
|
|
|
|
*/
|
2015-04-13 05:41:18 +02:00
|
|
|
public function Link($action = '') {
|
|
|
|
if($parent = $this->getParent()) {
|
2015-03-27 05:40:00 +01:00
|
|
|
return $parent->Link($action) . '#' . $this->Permalink();
|
|
|
|
}
|
2010-12-06 11:09:04 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-06 11:09:04 +01:00
|
|
|
/**
|
|
|
|
* Returns the permalink for this {@link Comment}. Inserted into
|
|
|
|
* the ID tag of the comment
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2010-12-07 01:34:17 +01:00
|
|
|
public function Permalink() {
|
2015-04-07 05:56:00 +02:00
|
|
|
$prefix = $this->getOption('comment_permalink_prefix');
|
2010-12-07 01:34:17 +01:00
|
|
|
return $prefix . $this->ID;
|
2010-11-29 23:24:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
2012-07-22 03:30:33 +02:00
|
|
|
* Translate the form field labels for the CMS administration
|
|
|
|
*
|
|
|
|
* @param boolean $includerelations
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
|
|
|
* @return array
|
2010-11-29 23:24:17 +01:00
|
|
|
*/
|
2012-07-22 03:30:33 +02:00
|
|
|
public function fieldLabels($includerelations = true) {
|
2010-11-29 23:24:17 +01:00
|
|
|
$labels = parent::fieldLabels($includerelations);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-07 01:34:17 +01:00
|
|
|
$labels['Name'] = _t('Comment.NAME', 'Author Name');
|
|
|
|
$labels['Comment'] = _t('Comment.COMMENT', 'Comment');
|
2013-06-20 15:40:25 +02:00
|
|
|
$labels['Email'] = _t('Comment.EMAIL', 'Email');
|
|
|
|
$labels['URL'] = _t('Comment.URL', 'URL');
|
2010-12-07 01:34:17 +01:00
|
|
|
$labels['IsSpam'] = _t('Comment.ISSPAM', 'Spam?');
|
2012-07-22 03:50:17 +02:00
|
|
|
$labels['Moderated'] = _t('Comment.MODERATED', 'Moderated?');
|
2013-06-20 15:40:25 +02:00
|
|
|
$labels['ParentTitle'] = _t('Comment.PARENTTITLE', 'Parent');
|
|
|
|
$labels['Created'] = _t('Comment.CREATED', 'Date posted');
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
return $labels;
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the commenting option
|
|
|
|
*
|
|
|
|
* @param string $key
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-04-07 05:56:00 +02:00
|
|
|
* @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);
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-12-07 01:34:17 +01:00
|
|
|
/**
|
|
|
|
* Returns the parent {@link DataObject} this comment is attached too
|
|
|
|
*
|
|
|
|
* @return DataObject
|
|
|
|
*/
|
|
|
|
public function getParent() {
|
2015-04-07 05:56:00 +02:00
|
|
|
return $this->BaseClass && $this->ParentID
|
|
|
|
? DataObject::get_by_id($this->BaseClass, $this->ParentID, true)
|
|
|
|
: null;
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2012-06-01 08:34:31 +02:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns a string to help identify the parent of the comment
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2013-03-05 10:01:42 +01:00
|
|
|
public function getParentTitle() {
|
2015-04-07 05:56:00 +02:00
|
|
|
if($parent = $this->getParent()) {
|
2015-04-13 05:41:18 +02:00
|
|
|
return $parent->Title ?: ($parent->ClassName . ' #' . $parent->ID);
|
2015-04-07 05:56:00 +02:00
|
|
|
}
|
2012-06-01 08:34:31 +02:00
|
|
|
}
|
2013-03-05 08:34:15 +01:00
|
|
|
|
2012-12-17 20:53:16 +01:00
|
|
|
/**
|
2015-04-07 05:56:00 +02:00
|
|
|
* Comment-parent classnames obviously vary, return the parent classname
|
2012-12-17 20:53:16 +01:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getParentClassName() {
|
|
|
|
return $this->BaseClass;
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
|
|
|
public function castingHelper($field) {
|
|
|
|
// Safely escape the comment
|
|
|
|
if($field === 'EscapedComment') {
|
2015-04-07 05:56:00 +02:00
|
|
|
return $this->AllowHtml ? 'HTMLText' : 'Text';
|
2015-04-07 05:56:00 +02:00
|
|
|
}
|
|
|
|
return parent::castingHelper($field);
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2013-03-05 10:01:42 +01:00
|
|
|
/**
|
2015-04-07 05:56:00 +02:00
|
|
|
* Content to be safely escaped on the frontend
|
2013-03-05 10:01:42 +01:00
|
|
|
*
|
2015-04-07 05:56:00 +02:00
|
|
|
* @return string
|
2013-03-05 10:01:42 +01:00
|
|
|
*/
|
|
|
|
public function getEscapedComment() {
|
2015-04-07 05:56:00 +02:00
|
|
|
return $this->Comment;
|
2013-03-05 10:01:42 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return whether this comment is a preview (has not been written to the db)
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function isPreview() {
|
2015-04-07 05:56:00 +02:00
|
|
|
return !$this->exists();
|
2013-03-05 10:01:42 +01:00
|
|
|
}
|
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
2010-12-06 11:09:04 +01:00
|
|
|
* @todo needs to compare to the new {@link Commenting} configuration API
|
|
|
|
*
|
2015-04-13 05:41:18 +02:00
|
|
|
* @param Member $member
|
|
|
|
*
|
|
|
|
* @return bool
|
2010-11-29 23:24:17 +01:00
|
|
|
*/
|
2012-07-22 03:30:33 +02:00
|
|
|
public function canCreate($member = null) {
|
2010-11-30 10:49:21 +01:00
|
|
|
return false;
|
2010-11-29 23:24:17 +01:00
|
|
|
}
|
2010-11-30 10:49:21 +01:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
2015-04-13 05:41:18 +02:00
|
|
|
* Checks for association with a page, and {@link SiteTree->ProvidePermission}
|
2012-07-22 03:30:33 +02:00
|
|
|
* flag being set to true.
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2010-11-29 23:24:17 +01:00
|
|
|
* @param Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2010-11-29 23:24:17 +01:00
|
|
|
* @return Boolean
|
|
|
|
*/
|
2012-07-22 03:30:33 +02:00
|
|
|
public function canView($member = null) {
|
2015-04-14 06:25:48 +02:00
|
|
|
$member = $this->getMember($member);
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
$extended = $this->extendedCan('canView', $member);
|
2015-04-14 06:25:48 +02:00
|
|
|
if($extended !== null) {
|
|
|
|
return $extended;
|
|
|
|
}
|
2012-07-31 10:45:29 +02:00
|
|
|
|
2015-04-14 06:25:48 +02:00
|
|
|
if(Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
|
|
|
|
return true;
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
2015-04-14 06:25:48 +02:00
|
|
|
if($parent = $this->getParent()) {
|
|
|
|
return $parent->canView($member)
|
|
|
|
&& $parent->has_extension('CommentsExtension')
|
|
|
|
&& $parent->CommentsEnabled;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
2010-11-29 23:24:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
2015-04-14 06:25:48 +02:00
|
|
|
* Checks if the comment can be edited.
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-04-14 06:25:48 +02:00
|
|
|
* @param null|int|Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2010-11-29 23:24:17 +01:00
|
|
|
* @return Boolean
|
|
|
|
*/
|
2012-07-22 03:30:33 +02:00
|
|
|
public function canEdit($member = null) {
|
2015-04-14 06:25:48 +02:00
|
|
|
$member = $this->getMember($member);
|
|
|
|
|
|
|
|
if(!$member) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
$extended = $this->extendedCan('canEdit', $member);
|
2015-04-14 06:25:48 +02:00
|
|
|
if($extended !== null) {
|
|
|
|
return $extended;
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2015-04-14 06:25:48 +02:00
|
|
|
if(Permission::checkMember($member, 'CMS_ACCESS_CommentAdmin')) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if($parent = $this->getParent()) {
|
|
|
|
return $parent->canEdit($member);
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2015-04-14 06:25:48 +02:00
|
|
|
return false;
|
2010-11-29 23:24:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
/**
|
2015-04-14 06:25:48 +02:00
|
|
|
* Checks if the comment can be deleted.
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-04-14 06:25:48 +02:00
|
|
|
* @param null|int|Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2010-11-29 23:24:17 +01:00
|
|
|
* @return Boolean
|
|
|
|
*/
|
2012-07-22 03:30:33 +02:00
|
|
|
public function canDelete($member = null) {
|
2015-04-14 06:25:48 +02:00
|
|
|
$member = $this->getMember($member);
|
|
|
|
|
|
|
|
if(!$member) {
|
|
|
|
return false;
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
$extended = $this->extendedCan('canDelete', $member);
|
2015-04-14 06:25:48 +02:00
|
|
|
if($extended !== null) {
|
|
|
|
return $extended;
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2010-11-29 23:24:17 +01:00
|
|
|
return $this->canEdit($member);
|
|
|
|
}
|
2012-07-22 03:30:33 +02:00
|
|
|
|
2015-04-14 06:25:48 +02:00
|
|
|
/**
|
|
|
|
* Resolves Member object.
|
|
|
|
*
|
|
|
|
* @param Member|int|null $member
|
|
|
|
* @return Member|null
|
|
|
|
*/
|
|
|
|
protected function getMember($member = null) {
|
|
|
|
if(!$member) {
|
|
|
|
$member = Member::currentUser();
|
|
|
|
}
|
|
|
|
|
|
|
|
if(is_numeric($member)) {
|
|
|
|
$member = DataObject::get_by_id('Member', $member, true);
|
|
|
|
}
|
|
|
|
|
|
|
|
return $member;
|
|
|
|
}
|
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
|
|
|
* Return the authors name for the comment
|
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getAuthorName() {
|
2010-12-07 01:34:17 +01:00
|
|
|
if($this->Name) {
|
|
|
|
return $this->Name;
|
2015-04-07 05:56:00 +02:00
|
|
|
} else if($author = $this->Author()) {
|
|
|
|
return $author->getName();
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
2015-03-27 05:40:00 +01:00
|
|
|
* Generate a secure admin-action link authorised for the specified member
|
|
|
|
*
|
|
|
|
* @param string $action An action on CommentingController to link to
|
|
|
|
* @param Member $member The member authorised to invoke this action
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2012-07-22 03:30:33 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2015-03-27 05:40:00 +01:00
|
|
|
protected function actionLink($action, $member = null) {
|
|
|
|
if(!$member) $member = Member::currentUser();
|
|
|
|
if(!$member) return false;
|
2012-07-22 03:30:33 +02:00
|
|
|
|
2015-03-27 05:40:00 +01:00
|
|
|
$url = Controller::join_links(
|
|
|
|
Director::baseURL(),
|
2015-04-13 05:41:18 +02:00
|
|
|
'CommentingController',
|
2015-03-27 05:40:00 +01:00
|
|
|
$action,
|
|
|
|
$this->ID
|
|
|
|
);
|
|
|
|
|
|
|
|
// Limit access for this user
|
|
|
|
$token = $this->getSecurityToken();
|
|
|
|
return $token->addToUrl($url, $member);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Link to delete this comment
|
|
|
|
*
|
|
|
|
* @param Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-03-27 05:40:00 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function DeleteLink($member = null) {
|
2015-04-01 03:58:03 +02:00
|
|
|
if($this->canDelete($member)) {
|
|
|
|
return $this->actionLink('delete', $member);
|
|
|
|
}
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
2015-03-27 05:40:00 +01:00
|
|
|
* Link to mark as spam
|
|
|
|
*
|
|
|
|
* @param Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2012-07-22 03:30:33 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2015-03-27 05:40:00 +01:00
|
|
|
public function SpamLink($member = null) {
|
2015-04-01 03:58:03 +02:00
|
|
|
if($this->canEdit($member) && !$this->IsSpam) {
|
|
|
|
return $this->actionLink('spam', $member);
|
|
|
|
}
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
2015-03-27 05:40:00 +01:00
|
|
|
* Link to mark as not-spam (ham)
|
|
|
|
*
|
|
|
|
* @param Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2012-07-22 03:30:33 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2015-03-27 05:40:00 +01:00
|
|
|
public function HamLink($member = null) {
|
2015-04-01 03:58:03 +02:00
|
|
|
if($this->canEdit($member) && $this->IsSpam) {
|
|
|
|
return $this->actionLink('ham', $member);
|
|
|
|
}
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
2015-03-27 05:40:00 +01:00
|
|
|
* Link to approve this comment
|
|
|
|
*
|
|
|
|
* @param Member $member
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2012-07-22 03:30:33 +02:00
|
|
|
* @return string
|
|
|
|
*/
|
2015-03-27 05:40:00 +01:00
|
|
|
public function ApproveLink($member = null) {
|
2015-04-01 03:58:03 +02:00
|
|
|
if($this->canEdit($member) && !$this->Moderated) {
|
|
|
|
return $this->actionLink('approve', $member);
|
|
|
|
}
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2015-04-16 02:48:30 +02:00
|
|
|
/**
|
|
|
|
* Mark this comment as spam
|
|
|
|
*/
|
|
|
|
public function markSpam() {
|
|
|
|
$this->IsSpam = true;
|
|
|
|
$this->Moderated = true;
|
|
|
|
$this->write();
|
|
|
|
$this->extend('afterMarkSpam');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark this comment as approved
|
|
|
|
*/
|
|
|
|
public function markApproved() {
|
|
|
|
$this->IsSpam = false;
|
|
|
|
$this->Moderated = true;
|
|
|
|
$this->write();
|
|
|
|
$this->extend('afterMarkApproved');
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Mark this comment as unapproved
|
|
|
|
*/
|
|
|
|
public function markUnapproved() {
|
|
|
|
$this->Moderated = false;
|
|
|
|
$this->write();
|
|
|
|
$this->extend('afterMarkUnapproved');
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function SpamClass() {
|
2012-07-31 10:45:29 +02:00
|
|
|
if($this->IsSpam) {
|
2010-12-07 01:34:17 +01:00
|
|
|
return 'spam';
|
2012-07-31 10:45:29 +02:00
|
|
|
} else if(!$this->Moderated) {
|
2010-12-07 01:34:17 +01:00
|
|
|
return 'unmoderated';
|
|
|
|
} else {
|
|
|
|
return 'notspam';
|
|
|
|
}
|
|
|
|
}
|
2015-04-13 05:41:18 +02:00
|
|
|
|
2012-07-22 03:30:33 +02:00
|
|
|
/**
|
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function getTitle() {
|
2015-04-13 05:41:18 +02:00
|
|
|
$title = sprintf(_t('Comment.COMMENTBY', 'Comment by %s', 'Name'), $this->getAuthorName());
|
2012-07-22 03:30:33 +02:00
|
|
|
|
|
|
|
if($parent = $this->getParent()) {
|
|
|
|
if($parent->Title) {
|
2015-04-13 05:41:18 +02:00
|
|
|
$title .= sprintf(' %s %s', _t('Comment.ON', 'on'), $parent->Title);
|
2012-07-22 03:30:33 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $title;
|
2010-12-07 01:34:17 +01:00
|
|
|
}
|
2012-12-17 20:53:16 +01:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Modify the default fields shown to the user
|
|
|
|
*/
|
|
|
|
public function getCMSFields() {
|
2015-04-07 05:56:00 +02:00
|
|
|
$commentField = $this->AllowHtml ? 'HtmlEditorField' : 'TextareaField';
|
|
|
|
$fields = new FieldList(
|
|
|
|
$this
|
|
|
|
->obj('Created')
|
|
|
|
->scaffoldFormField($this->fieldLabel('Created'))
|
|
|
|
->performReadonlyTransformation(),
|
|
|
|
TextField::create('Name', $this->fieldLabel('AuthorName')),
|
|
|
|
$commentField::create('Comment', $this->fieldLabel('Comment')),
|
|
|
|
EmailField::create('Email', $this->fieldLabel('Email')),
|
|
|
|
TextField::create('URL', $this->fieldLabel('URL')),
|
|
|
|
FieldGroup::create(array(
|
|
|
|
CheckboxField::create('Moderated', $this->fieldLabel('Moderated')),
|
|
|
|
CheckboxField::create('IsSpam', $this->fieldLabel('IsSpam')),
|
|
|
|
))
|
|
|
|
->setTitle('Options')
|
|
|
|
->setDescription(_t(
|
|
|
|
'Comment.OPTION_DESCRIPTION',
|
|
|
|
'Unmoderated and spam comments will not be displayed until approved'
|
|
|
|
))
|
|
|
|
);
|
2013-02-21 16:39:57 +01:00
|
|
|
|
2015-04-07 05:56:00 +02:00
|
|
|
// Show member name if given
|
|
|
|
if(($author = $this->Author()) && $author->exists()) {
|
|
|
|
$fields->insertAfter(
|
|
|
|
TextField::create('AuthorMember', $this->fieldLabel('Author'), $author->Title)
|
|
|
|
->performReadonlyTransformation(),
|
|
|
|
'Name'
|
|
|
|
);
|
|
|
|
}
|
2013-03-05 10:01:42 +01:00
|
|
|
|
2015-04-07 05:56:00 +02:00
|
|
|
$this->extend('updateCMSFields', $fields);
|
2013-03-05 10:01:42 +01:00
|
|
|
return $fields;
|
2013-02-21 16:39:57 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param String $dirtyHtml
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2013-02-21 16:39:57 +01:00
|
|
|
* @return String
|
|
|
|
*/
|
|
|
|
public function purifyHtml($dirtyHtml) {
|
|
|
|
$purifier = $this->getHtmlPurifierService();
|
|
|
|
return $purifier->purify($dirtyHtml);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @return HTMLPurifier (or anything with a "purify()" method)
|
|
|
|
*/
|
|
|
|
public function getHtmlPurifierService() {
|
|
|
|
$config = HTMLPurifier_Config::createDefault();
|
2015-04-07 05:56:00 +02:00
|
|
|
$config->set('HTML.AllowedElements', $this->getOption('html_allowed_elements'));
|
2013-02-21 16:39:57 +01:00
|
|
|
$config->set('AutoFormat.AutoParagraph', true);
|
|
|
|
$config->set('AutoFormat.Linkify', true);
|
|
|
|
$config->set('URI.DisableExternalResources', true);
|
2015-01-12 02:00:20 +01:00
|
|
|
$config->set('Cache.SerializerPath', getTempFolder());
|
2013-02-21 16:39:57 +01:00
|
|
|
return new HTMLPurifier($config);
|
|
|
|
}
|
2013-03-06 09:50:41 +01:00
|
|
|
|
2015-04-07 05:56:00 +02:00
|
|
|
/**
|
2015-04-13 05:41:18 +02:00
|
|
|
* Calculate the Gravatar link from the email address
|
2015-04-07 05:56:00 +02:00
|
|
|
*
|
|
|
|
* @return string
|
|
|
|
*/
|
2013-03-06 09:50:41 +01:00
|
|
|
public function Gravatar() {
|
|
|
|
$gravatar = '';
|
2015-04-07 05:56:00 +02:00
|
|
|
$use_gravatar = $this->getOption('use_gravatar');
|
2015-04-13 05:41:18 +02:00
|
|
|
if($use_gravatar) {
|
|
|
|
$gravatar = 'http://www.gravatar.com/avatar/' . md5(strtolower(trim($this->Email)));
|
2015-04-07 05:56:00 +02:00
|
|
|
$gravatarsize = $this->getOption('gravatar_size');
|
|
|
|
$gravatardefault = $this->getOption('gravatar_default');
|
|
|
|
$gravatarrating = $this->getOption('gravatar_rating');
|
2015-04-13 05:41:18 +02:00
|
|
|
$gravatar .= '?s=' . $gravatarsize . '&d=' . $gravatardefault . '&r=' . $gravatarrating;
|
2013-03-06 09:50:41 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return $gravatar;
|
|
|
|
}
|
2015-04-07 05:56:00 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if replies are enabled for this instance
|
|
|
|
*
|
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function getRepliesEnabled() {
|
|
|
|
// Check reply option
|
|
|
|
if(!$this->getOption('nested_comments')) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check if depth is limited
|
|
|
|
$maxLevel = $this->getOption('nested_depth');
|
|
|
|
return !$maxLevel || $this->Depth < $maxLevel;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the list of all replies
|
|
|
|
*
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
|
|
|
public function AllReplies() {
|
|
|
|
// No replies if disabled
|
|
|
|
if(!$this->getRepliesEnabled()) {
|
|
|
|
return new ArrayList();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all non-spam comments
|
|
|
|
$order = $this->getOption('order_replies_by')
|
|
|
|
?: $this->getOption('order_comments_by');
|
|
|
|
$list = $this
|
|
|
|
->ChildComments()
|
|
|
|
->sort($order);
|
|
|
|
|
|
|
|
$this->extend('updateAllReplies', $list);
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the list of replies, with spam and unmoderated items excluded, for use in the frontend
|
|
|
|
*
|
|
|
|
* @return SS_List
|
|
|
|
*/
|
|
|
|
public function Replies() {
|
|
|
|
// No replies if disabled
|
|
|
|
if(!$this->getRepliesEnabled()) {
|
|
|
|
return new ArrayList();
|
|
|
|
}
|
|
|
|
$list = $this->AllReplies();
|
|
|
|
|
|
|
|
// Filter spam comments for non-administrators if configured
|
|
|
|
$parent = $this->getParent();
|
|
|
|
$showSpam = $this->getOption('frontend_spam') && $parent && $parent->canModerateComments();
|
|
|
|
if(!$showSpam) {
|
|
|
|
$list = $list->filter('IsSpam', 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Filter un-moderated comments for non-administrators if moderation is enabled
|
|
|
|
$showUnmoderated = $parent && (
|
|
|
|
($parent->ModerationRequired === 'None')
|
|
|
|
|| ($this->getOption('frontend_moderation') && $parent->canModerateComments())
|
|
|
|
);
|
|
|
|
if (!$showUnmoderated) {
|
|
|
|
$list = $list->filter('Moderated', 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->extend('updateReplies', $list);
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the list of replies paged, with spam and unmoderated items excluded, for use in the frontend
|
|
|
|
*
|
|
|
|
* @return PaginatedList
|
|
|
|
*/
|
|
|
|
public function PagedReplies() {
|
|
|
|
$list = $this->Replies();
|
|
|
|
|
|
|
|
// Add pagination
|
|
|
|
$list = new PaginatedList($list, Controller::curr()->getRequest());
|
|
|
|
$list->setPaginationGetVar('repliesstart'.$this->ID);
|
|
|
|
$list->setPageLength($this->getOption('comments_per_page'));
|
|
|
|
|
|
|
|
$this->extend('updatePagedReplies', $list);
|
|
|
|
return $list;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate a reply form for this comment
|
|
|
|
*
|
|
|
|
* @return Form
|
|
|
|
*/
|
|
|
|
public function ReplyForm() {
|
|
|
|
// Ensure replies are enabled
|
|
|
|
if(!$this->getRepliesEnabled()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check parent is available
|
|
|
|
$parent = $this->getParent();
|
|
|
|
if(!$parent || !$parent->exists()) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Build reply controller
|
|
|
|
$controller = CommentingController::create();
|
|
|
|
$controller->setOwnerRecord($parent);
|
|
|
|
$controller->setBaseClass($parent->ClassName);
|
|
|
|
$controller->setOwnerController(Controller::curr());
|
|
|
|
|
|
|
|
return $controller->ReplyForm($this);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Refresh of this comment in the hierarchy
|
|
|
|
*/
|
|
|
|
public function updateDepth() {
|
|
|
|
$parent = $this->ParentComment();
|
|
|
|
if($parent && $parent->exists()) {
|
|
|
|
$parent->updateDepth();
|
|
|
|
$this->Depth = $parent->Depth + 1;
|
|
|
|
} else {
|
|
|
|
$this->Depth = 1;
|
|
|
|
}
|
|
|
|
}
|
2012-03-17 00:21:58 +01:00
|
|
|
}
|
2015-03-27 05:40:00 +01:00
|
|
|
|
2015-04-07 05:56:00 +02:00
|
|
|
|
2015-03-27 05:40:00 +01:00
|
|
|
/**
|
|
|
|
* Provides the ability to generate cryptographically secure tokens for comment moderation
|
|
|
|
*/
|
|
|
|
class Comment_SecurityToken {
|
|
|
|
|
|
|
|
private $secret = null;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param Comment $comment Comment to generate this token for
|
|
|
|
*/
|
|
|
|
public function __construct($comment) {
|
|
|
|
if(!$comment->SecretToken) {
|
|
|
|
$comment->SecretToken = $this->generate();
|
|
|
|
$comment->write();
|
|
|
|
}
|
|
|
|
$this->secret = $comment->SecretToken;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generate the token for the given salt and current secret
|
|
|
|
*
|
|
|
|
* @param string $salt
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-03-27 05:40:00 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function getToken($salt) {
|
|
|
|
return function_exists('hash_pbkdf2')
|
|
|
|
? hash_pbkdf2('sha256', $this->secret, $salt, 1000, 30)
|
|
|
|
: $this->hash_pbkdf2('sha256', $this->secret, $salt, 100, 30);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get the member-specific salt.
|
|
|
|
*
|
2015-04-13 05:41:18 +02:00
|
|
|
* The reason for making the salt specific to a user is that it cannot be "passed in" via a
|
|
|
|
* querystring, requiring the same user to be present at both the link generation and the
|
|
|
|
* controller action.
|
|
|
|
*
|
|
|
|
* @param string $salt Single use salt
|
|
|
|
* @param Member $member Member object
|
2015-03-27 05:40:00 +01:00
|
|
|
*
|
|
|
|
* @return string Generated salt specific to this member
|
|
|
|
*/
|
|
|
|
protected function memberSalt($salt, $member) {
|
|
|
|
// Fallback to salting with ID in case the member has not one set
|
|
|
|
return $salt . ($member->Salt ?: $member->ID);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2015-04-13 05:41:18 +02:00
|
|
|
* @param string $url Comment action URL
|
2015-03-27 05:40:00 +01:00
|
|
|
* @param Member $member Member to restrict access to this action to
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-03-27 05:40:00 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
public function addToUrl($url, $member) {
|
|
|
|
$salt = $this->generate(15); // New random salt; Will be passed into url
|
|
|
|
// Generate salt specific to this member
|
|
|
|
$memberSalt = $this->memberSalt($salt, $member);
|
|
|
|
$token = $this->getToken($memberSalt);
|
|
|
|
return Controller::join_links(
|
|
|
|
$url,
|
|
|
|
sprintf(
|
|
|
|
'?t=%s&s=%s',
|
|
|
|
urlencode($token),
|
|
|
|
urlencode($salt)
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @param SS_HTTPRequest $request
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-03-27 05:40:00 +01:00
|
|
|
* @return boolean
|
|
|
|
*/
|
|
|
|
public function checkRequest($request) {
|
|
|
|
$member = Member::currentUser();
|
|
|
|
if(!$member) return false;
|
|
|
|
|
|
|
|
$salt = $request->getVar('s');
|
|
|
|
$memberSalt = $this->memberSalt($salt, $member);
|
|
|
|
$token = $this->getToken($memberSalt);
|
|
|
|
|
|
|
|
// Ensure tokens match
|
|
|
|
return $token === $request->getVar('t');
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Generates new random key
|
|
|
|
*
|
|
|
|
* @param integer $length
|
2015-04-13 05:41:18 +02:00
|
|
|
*
|
2015-03-27 05:40:00 +01:00
|
|
|
* @return string
|
|
|
|
*/
|
|
|
|
protected function generate($length = null) {
|
|
|
|
$generator = new RandomGenerator();
|
|
|
|
$result = $generator->randomToken('sha256');
|
2015-04-13 05:41:18 +02:00
|
|
|
if($length !== null) return substr($result, 0, $length);
|
2015-03-27 05:40:00 +01:00
|
|
|
return $result;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*-----------------------------------------------------------
|
|
|
|
* PBKDF2 Implementation (described in RFC 2898) from php.net
|
|
|
|
*-----------------------------------------------------------
|
|
|
|
* @param string a hash algorithm
|
|
|
|
* @param string p password
|
|
|
|
* @param string s salt
|
|
|
|
* @param int c iteration count (use 1000 or higher)
|
|
|
|
* @param int kl derived key length
|
|
|
|
* @param int st start position of result
|
|
|
|
*
|
|
|
|
* @return string derived key
|
|
|
|
*/
|
2015-04-13 05:41:18 +02:00
|
|
|
private function hash_pbkdf2($a, $p, $s, $c, $kl, $st = 0) {
|
2015-03-27 05:40:00 +01:00
|
|
|
|
2015-04-13 05:41:18 +02:00
|
|
|
$kb = $st + $kl; // Key blocks to compute
|
|
|
|
$dk = ''; // Derived key
|
2015-03-27 05:40:00 +01:00
|
|
|
|
|
|
|
// Create key
|
2015-04-13 05:41:18 +02:00
|
|
|
for($block = 1; $block <= $kb; $block++) {
|
2015-03-27 05:40:00 +01:00
|
|
|
|
|
|
|
// Initial hash for this block
|
|
|
|
$ib = $h = hash_hmac($a, $s . pack('N', $block), $p, true);
|
|
|
|
|
|
|
|
// Perform block iterations
|
2015-04-13 05:41:18 +02:00
|
|
|
for($i = 1; $i < $c; $i++) {
|
2015-03-27 05:40:00 +01:00
|
|
|
// XOR each iterate
|
2015-04-13 05:41:18 +02:00
|
|
|
$ib ^= ($h = hash_hmac($a, $h, $p, true));
|
2015-03-27 05:40:00 +01:00
|
|
|
}
|
|
|
|
|
2015-04-13 05:41:18 +02:00
|
|
|
$dk .= $ib; // Append iterated block
|
2015-03-27 05:40:00 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return derived key of correct length
|
|
|
|
return substr($dk, $st, $kl);
|
|
|
|
}
|
|
|
|
}
|