diff --git a/code/Commenting.php b/code/Commenting.php index db86570..322b552 100644 --- a/code/Commenting.php +++ b/code/Commenting.php @@ -66,7 +66,16 @@ class Commenting { Object::remove_extension($class, 'CommentsExtension'); } - + + /** + * Returns whether a given class name has commenting enabled + * + * @return bool + */ + public static function has_commenting($class) { + return (isset(self::$enabled_classes[$class])); + } + /** * Sets a value for a class of a given config setting. Passing 'all' as the class * sets it for everything diff --git a/code/controllers/CommentingController.php b/code/controllers/CommentingController.php index 507f183..1fcc7bd 100644 --- a/code/controllers/CommentingController.php +++ b/code/controllers/CommentingController.php @@ -6,8 +6,12 @@ class CommentingController extends Controller { - static $allowed_actions = array( + public static $allowed_actions = array( 'delete', + 'spam', + 'ham', + 'approve', + 'rss', 'CommentsForm', 'doPostComment' ); @@ -42,38 +46,194 @@ class CommentingController extends Controller { /** * Workaround for generating the link to this controller + * + * @return string */ - public function Link($action = "") { - return __CLASS__ .'/'. $action; + public function Link($action = "", $id = '', $other = '') { + return Controller::join_links(__CLASS__ , $action, $id, $other); } /** - * Performs the delete task for deleting {@link Comment}. + * Return an RSS feed of comments for a given set of comments or all + * comments on the website. * - * /delete/$ID deletes the $ID comment + * To maintain backwards compatibility with 2.4 this supports mapping + * of PageComment/rss?pageid= as well as the new RSS format for comments + * of CommentingController/rss/{classname}/{id} + * + * @return RSS */ - public function delete() { + public function rss() { + $link = $this->Link('rss'); + $class = $this->urlParams['ID']; + $id = $this->urlParams['OtherID']; + + if(isset($_GET['pageid'])) { + $id = Convert::raw2sql($_GET['pageid']); + + $comments = Comment::get()->where(sprintf( + "BaseClass = 'SiteTree' AND ParentID = '%s'", $id + )); + + $link = $this->Link('rss', 'SiteTree', $id); + + } else if($class && $id) { + if(Commenting::has_commenting($class)) { + $comments = Comment::get()->where(sprintf( + "BaseClass = '%s' AND ParentID = '%s'", + Convert::raw2sql($class), + Convert::raw2sql($id) + )); + + $link = $this->Link('rss', Convert::raw2xml($class), (int) $id); + } else { + return $this->httpError(404); + } + } else if($class) { + if(Commenting::has_commenting($class)) { + $comments = Comment::get()->where(sprintf( + "BaseClass = '%s'", Convert::raw2sql($class) + )); + } else { + return $this->httpError(404); + } + } else { + $comments = Comment::get(); + } + + $title = _t('CommentingController.RSSTITLE', "Comments RSS Feed"); + + $feed = new RSSFeed($comments, $link, $title, $link, 'Title', 'Comment', 'AuthorName'); + $feed->outputToBrowser(); + } + + /** + * Deletes a given {@link Comment} via the URL. + * + * @param SS_HTTPRequest + */ + public function delete($request) { + if(!$this->checkSecurityToken($request)) { + return $this->httpError(400); + } + + if(($comment = $this->getComment()) && $comment->canDelete()) { + $comment->delete(); + + return ($this->request->isAjax()) ? true : $this->redirectBack(); + } + + return $this->httpError(404); + } + + /** + * Marks a given {@link Comment} as spam. Removes the comment from display + * + * @param SS_HTTPRequest + */ + public function spam() { + if(!$this->checkSecurityToken($request)) { + return $this->httpError(400); + } + + $comment = $this->getComment(); + + if(($comment = $this->getComment()) && $comment->canEdit()) { + $comment->IsSpam = true; + $comment->Moderated = true; + $comment->write(); + + return ($this->request->isAjax()) ? true : $this->redirectBack(); + } + + return $this->httpError(404); + } + + /** + * Marks a given {@link Comment} as ham (not spam). + * + * @param SS_HTTPRequest + */ + public function ham($request) { + if(!$this->checkSecurityToken($request)) { + return $this->httpError(400); + } + + $comment = $this->getComment(); + + if(($comment = $this->getComment()) && $comment->canEdit()) { + $comment->IsSpam = false; + $comment->Moderated = true; + $comment->write(); + + return ($this->request->isAjax()) ? true : $this->redirectBack(); + } + + return $this->httpError(404); + } + + /** + * Marks a given {@link Comment} as approved. + * + * @param SS_HTTPRequest + */ + public function approve($request) { + if(!$this->checkSecurityToken($request)) { + return $this->httpError(400); + } + + $comment = $this->getComment(); + + if(($comment = $this->getComment()) && $comment->canEdit()) { + $comment->IsSpam = false; + $comment->Moderated = true; + $comment->write(); + + return ($this->request->isAjax()) ? true : $this->redirectBack(); + } + + return $this->httpError(404); + } + + /** + * Returns the comment referenced in the URL (by ID). + * + * @return Comment|false + */ + public function getComment() { $id = isset($this->urlParams['ID']) ? $this->urlParams['ID'] : false; if($id) { $comment = DataObject::get_by_id('Comment', $id); - if($comment && $comment->canDelete()) { - $comment->delete(); - - return ($this->request->isAjax()) ? true : $this->redirectBack(); + if($comment) { + return $comment; } } - return ($this->request->isAjax()) ? false : $this->httpError('404'); + return false; } - + + /** + * Checks the security token given with the URL to prevent CSRF attacks + * against administrators allowing users to hijack comment moderation. + * + * @param SS_HTTPRequest + * + * @return boolean + */ + public function checkSecurityToken($req) { + $token = SecurityToken::inst(); + + return $token->checkRequest($req); + } + /** * Post a comment form * * @return Form */ - function CommentsForm() { + public function CommentsForm() { $member = Member::currentUser(); $fields = new FieldList( @@ -143,6 +303,10 @@ class CommentingController extends Controller { "Comment" => Cookie::get('CommentsForm_Comment') )); } + + if($member) { + $form->loadDataFrom($member); + } // hook to allow further extensions to alter the comments form $this->extend('alterCommentForm', $form); @@ -156,7 +320,7 @@ class CommentingController extends Controller { * @param array $data * @param Form $form */ - function doPostComment($data, $form) { + public function doPostComment($data, $form) { $class = (isset($data['BaseClass'])) ? $data['BaseClass'] : $this->getBaseClass(); // if no class then we cannot work out what controller or model they diff --git a/code/dataobjects/Comment.php b/code/dataobjects/Comment.php index bed08c4..4e1f638 100755 --- a/code/dataobjects/Comment.php +++ b/code/dataobjects/Comment.php @@ -7,7 +7,7 @@ */ class Comment extends DataObject { - static $db = array( + public static $db = array( "Name" => "Varchar(200)", "Comment" => "Text", "Email" => "Varchar(200)", @@ -15,27 +15,29 @@ class Comment extends DataObject { "BaseClass" => "Varchar(200)", "Moderated" => "Boolean", "IsSpam" => "Boolean", - 'NeedsModeration' => 'Boolean', + "ParentID" => "Int" ); - static $has_one = array( - "Parent" => "DataObject", + public static $has_one = array( "Author" => "Member" ); - static $has_many = array(); + public static $default_sort = "Created DESC"; - static $many_many = array(); + public static $has_many = array(); - static $defaults = array( + public static $many_many = array(); + + public static $defaults = array( "Moderated" => true ); - static $casting = array( - "RSSTitle" => "Varchar", + public static $casting = array( + 'AuthorName' => 'Varchar', + 'RSSName' => 'Varchar' ); - static $searchable_fields = array( + public static $searchable_fields = array( 'Name', 'Email', 'Comment', @@ -43,12 +45,13 @@ class Comment extends DataObject { 'BaseClass', ); - static $summary_fields = array( + public static $summary_fields = array( 'Name' => 'Submitted By', 'Email' => 'Email', 'Comment' => 'Comment', 'Created' => 'Date Posted', 'ParentTitle' => 'Parent', + 'IsSpam' => 'Is Spam' ); @@ -87,7 +90,7 @@ class Comment extends DataObject { * @return string link to this comment. */ public function Link($action = "") { - return $this->Parent()->Link($action) . '#' . $this->Permalink(); + return $this->getParent()->Link($action) . '#' . $this->Permalink(); } /** @@ -103,9 +106,11 @@ class Comment extends DataObject { } /** - * @param boolean $includerelations a boolean value to indicate if the labels returned include relation fields + * Translate the form field labels for the CMS administration + * + * @param boolean $includerelations */ - function fieldLabels($includerelations = true) { + public function fieldLabels($includerelations = true) { $labels = parent::fieldLabels($includerelations); $labels['Name'] = _t('Comment.NAME', 'Author Name'); $labels['Comment'] = _t('Comment.COMMENT', 'Comment'); @@ -132,8 +137,9 @@ class Comment extends DataObject { * * @return string */ - function getParentTitle(){ + public function getParentTitle(){ $parent = $this->getParent(); + return ($parent->Title) ? $parent->Title : $parent->ClassName . " #" . $parent->ID; } @@ -142,20 +148,18 @@ class Comment extends DataObject { * * @return Boolean */ - function canCreate($member = null) { + public function canCreate($member = null) { return false; } /** - * Checks for association with a page, - * and {@link SiteTree->ProvidePermission} flag being set to TRUE. - * Note: There's an additional layer of permission control - * in {@link PageCommentInterface}. + * Checks for association with a page, and {@link SiteTree->ProvidePermission} + * flag being set to true. * * @param Member $member * @return Boolean */ - function canView($member = null) { + public function canView($member = null) { if(!$member) $member = Member::currentUser(); // Standard mechanism for accepting permission changes from decorators @@ -170,13 +174,13 @@ class Comment extends DataObject { } /** - * Checks for "CMS_ACCESS_CommentAdmin" permission codes - * and {@link canView()}. + * Checks for "CMS_ACCESS_CommentAdmin" permission codes and + * {@link canView()}. * * @param Member $member * @return Boolean */ - function canEdit($member = null) { + public function canEdit($member = null) { if(!$member) $member = Member::currentUser(); // Standard mechanism for accepting permission changes from decorators @@ -189,13 +193,13 @@ class Comment extends DataObject { } /** - * Checks for "CMS_ACCESS_CommentAdmin" permission codes - * and {@link canEdit()}. + * Checks for "CMS_ACCESS_CommentAdmin" permission codes and + * {@link canEdit()}. * * @param Member $member * @return Boolean */ - function canDelete($member = null) { + public function canDelete($member = null) { if(!$member) $member = Member::currentUser(); // Standard mechanism for accepting permission changes from decorators @@ -204,40 +208,76 @@ class Comment extends DataObject { return $this->canEdit($member); } - - - /************************************ Review the following */ - function getRSSName() { + + /** + * Return the authors name for the comment + * + * @return string + */ + public function getAuthorName() { if($this->Name) { return $this->Name; - } elseif($this->Author()) { + } else if($this->Author()) { return $this->Author()->getName(); } } - function DeleteLink() { - return ($this->canDelete()) ? "PageComment_Controller/deletecomment/$this->ID" : false; + /** + * @return string + */ + public function DeleteLink() { + if($this->canDelete()) { + $token = SecurityToken::inst(); + + return DBField::create_field("Varchar", $token->addToUrl(sprintf( + "CommentingController/delete/%s", (int) $this->ID + ))); + } } - function CommentTextWithLinks() { - $pattern = '|([a-zA-Z]+://)([a-zA-Z0-9?&%.;:/=+_-]*)|is'; - $replace = '$1$2'; - return preg_replace($pattern, $replace, $this->Comment); + /** + * @return string + */ + public function SpamLink() { + if($this->canEdit() && !$this->IsSpam) { + $token = SecurityToken::inst(); + + return DBField::create_field("Varchar", $token->addToUrl(sprintf( + "CommentingController/spam/%s", (int) $this->ID + ))); + } } - function SpamLink() { - return ($this->canEdit() && !$this->IsSpam) ? "PageComment_Controller/reportspam/$this->ID" : false; + /** + * @return string + */ + public function HamLink() { + if($this->canEdit() && $this->IsSpam) { + $token = SecurityToken::inst(); + + return DBField::create_field("Varchar", $token->addToUrl(sprintf( + "CommentingController/ham/%s", (int) $this->ID + ))); + } } - function HamLink() { - return ($this->canEdit() && $this->IsSpam) ? "PageComment_Controller/reportham/$this->ID" : false; + /** + * @return string + */ + public function ApproveLink() { + if($this->canEdit() && !$this->Moderated) { + $token = SecurityToken::inst(); + + return DBField::create_field("Varchar", $token->addToUrl(sprintf( + "CommentingController/approve/%s", (int) $this->ID + ))); + } } - function ApproveLink() { - return ($this->canEdit() && $this->NeedsModeration) ? "PageComment_Controller/approve/$this->ID" : false; - } - - function SpamClass() { + /** + * @return string + */ + public function SpamClass() { if($this->getField('IsSpam')) { return 'spam'; } else if($this->getField('NeedsModeration')) { @@ -247,12 +287,18 @@ class Comment extends DataObject { } } - - function RSSTitle() { - return sprintf( - _t('PageComment.COMMENTBY', "Comment by '%s' on %s", PR_MEDIUM, 'Name, Page Title'), - Convert::raw2xml($this->getRSSName()), - $this->Parent()->Title - ); + /** + * @return string + */ + public function getTitle() { + $title = sprintf(_t('Comment.COMMENTBY', "Comment by '%s'", 'Name'), $this->getAuthorName()); + + if($parent = $this->getParent()) { + if($parent->Title) { + $title .= sprintf(" %s %s", _t('Comment.ON', 'on'), $parent->Title); + } + } + + return $title; } } diff --git a/code/extensions/CommentsExtension.php b/code/extensions/CommentsExtension.php index 21e7138..3bb32bb 100644 --- a/code/extensions/CommentsExtension.php +++ b/code/extensions/CommentsExtension.php @@ -15,7 +15,7 @@ class CommentsExtension extends DataExtension { * * @return array */ - function extraStatics($class = null, $extension = null) { + public function extraStatics($class = null, $extension = null) { $fields = array(); $relationships = array( @@ -99,9 +99,7 @@ class CommentsExtension extends DataExtension { // 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 = new CommentingController(); - - // tad bit messy but needed to ensure all data is available + $controller = new CommentingController(); $controller->setOwnerRecord($this->owner); $controller->setBaseClass($this->ownerBaseClass); $controller->setOwnerController(Controller::curr()); @@ -114,6 +112,8 @@ class CommentsExtension extends DataExtension { 'CommentHolderID' => Commenting::get_config_value($this->ownerBaseClass, 'comments_holder_id'), 'PostingRequiresPermission' => Commenting::get_config_value($this->ownerBaseClass, 'required_permission'), 'CanPost' => Commenting::can_member_post($this->ownerBaseClass), + 'RssLink' => "CommentingController/rss", + 'RssLinkPage' => "CommentingController/rss/". $this->ownerBaseClass . '/'.$this->owner->ID, 'CommentsEnabled' => $enabled, 'AddCommentForm' => $form, 'Comments' => $this->Comments() @@ -134,7 +134,7 @@ class CommentsExtension extends DataExtension { /** * @deprecated 1.0 Please use {@link CommentsExtension->CommentsForm()} */ - function PageComments() { + public function PageComments() { // This method is very commonly used, don't throw a warning just yet //user_error('$PageComments is deprecated. Please use $CommentsForm', E_USER_WARNING); diff --git a/templates/CommentsInterface_singlecomment.ss b/templates/CommentsInterface_singlecomment.ss index ebecb03..e0a8765 100755 --- a/templates/CommentsInterface_singlecomment.ss +++ b/templates/CommentsInterface_singlecomment.ss @@ -1,32 +1,28 @@ -
- <% if bbCodeEnabled %> - $ParsedBBCode - <% else %> - $Comment.XML - <% end_if %> -
+<% if $URL %> - <% _t('PBY','Posted by') %> $Name.XML, $Created.Nice ($Created.Ago) + <% _t('PBY','Posted by') %> $AuthorName.XML, $Created.Nice ($Created.Ago) <% else %> - <% _t('PBY','Posted by') %> $Name.XML, $Created.Nice ($Created.Ago) + <% _t('PBY','Posted by') %> $AuthorName.XML, $Created.Nice ($Created.Ago) <% end_if %>
-<% if ApproveLink || SpamLink || HamLink || DeleteLink %> +<% if $ApproveLink || $SpamLink || $HamLink || $DeleteLink %>
$Comment.XML
+