Merge branch '5.3'
authorMatthias Schmidt <gravatronics@live.com>
Wed, 12 May 2021 07:49:01 +0000 (09:49 +0200)
committerMatthias Schmidt <gravatronics@live.com>
Wed, 12 May 2021 07:49:01 +0000 (09:49 +0200)
1  2 
wcfsetup/install/files/lib/data/comment/CommentAction.class.php
wcfsetup/install/files/lib/data/comment/response/CommentResponseAction.class.php
wcfsetup/install/files/lib/system/moderation/queue/activation/CommentResponseModerationQueueActivationHandler.class.php
wcfsetup/install/files/lib/system/moderation/queue/report/CommentResponseModerationQueueReportHandler.class.php

index 905aa9834a80c6a4efd016ed48bb792b4a4f96f5,e53f4fee11244868a8a85c8ed8e4873361bd209c..ab14c6d2e780e5728f95e75958ea0da8daf0e385
@@@ -19,13 -16,11 +19,14 @@@ use wcf\system\exception\NamedUserExcep
  use wcf\system\exception\PermissionDeniedException;
  use wcf\system\exception\SystemException;
  use wcf\system\exception\UserInputException;
 +use wcf\system\flood\FloodControl;
  use wcf\system\html\input\HtmlInputProcessor;
  use wcf\system\moderation\queue\ModerationQueueActivationManager;
 -use wcf\system\reaction\ReactionHandler;
+ use wcf\system\moderation\queue\ModerationQueueManager;
 +use wcf\system\reaction\ReactionHandler;
  use wcf\system\user\activity\event\UserActivityEventHandler;
 +use wcf\system\user\notification\object\CommentResponseUserNotificationObject;
 +use wcf\system\user\notification\object\CommentUserNotificationObject;
  use wcf\system\user\notification\object\type\ICommentUserNotificationObjectType;
  use wcf\system\user\notification\object\type\IMultiRecipientCommentResponseOwnerUserNotificationObjectType;
  use wcf\system\user\notification\object\type\IMultiRecipientCommentUserNotificationObjectType;
@@@ -37,1416 -34,1283 +38,1421 @@@ use wcf\util\UserUtil
  
  /**
   * Executes comment-related actions.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2019 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @package   WoltLabSuite\Core\Data\Comment
 - * 
 - * @method    Comment                 create()
 - * @method    CommentEditor[]         getObjects()
 - * @method    CommentEditor           getSingleObject()
 + *
 + * @author  Alexander Ebert
 + * @copyright   2001-2020 WoltLab GmbH
 + * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @package WoltLabSuite\Core\Data\Comment
 + *
 + * @method  Comment         create()
 + * @method  CommentEditor[]     getObjects()
 + * @method  CommentEditor       getSingleObject()
   */
 -class CommentAction extends AbstractDatabaseObjectAction implements IMessageInlineEditorAction {
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $allowGuestAccess = ['addComment', 'addResponse', 'loadComment', 'loadComments', 'loadResponse', 'getGuestDialog'];
 -      
 -      /**
 -       * captcha object type used for comments
 -       * @var ObjectType
 -       */
 -      public $captchaObjectType;
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $className = CommentEditor::class;
 -      
 -      /**
 -       * comment object
 -       * @var Comment
 -       */
 -      protected $comment;
 -      
 -      /**
 -       * comment processor
 -       * @var ICommentManager
 -       */
 -      protected $commentProcessor;
 -      
 -      /**
 -       * @var HtmlInputProcessor
 -       */
 -      protected $htmlInputProcessor;
 -      
 -      /**
 -       * response object
 -       * @var CommentResponse
 -       */
 -      protected $response;
 -      
 -      /**
 -       * comment object created by addComment()
 -       * @var Comment
 -       */
 -      public $createdComment;
 -      
 -      /**
 -       * response object created by addResponse()
 -       * @var CommentResponse
 -       */
 -      public $createdResponse;
 -      
 -      /**
 -       * errors occurring through the validation of addComment or addResponse
 -       * @var array
 -       */
 -      public $validationErrors = [];
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function delete() {
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -              }
 -              
 -              // update counters
 -              /** @var ICommentManager[] $processors */
 -              $processors = [];
 -              $groupCommentIDs = $commentIDs = [];
 -              foreach ($this->getObjects() as $comment) {
 -                      if (!isset($processors[$comment->objectTypeID])) {
 -                              $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
 -                              $processors[$comment->objectTypeID] = $objectType->getProcessor();
 -                              
 -                              $groupCommentIDs[$comment->objectTypeID] = [];
 -                      }
 -                      
 -                      if (!$comment->isDisabled) {
 -                              $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1));
 -                      }
 -                      
 -                      $groupCommentIDs[$comment->objectTypeID][] = $comment->commentID;
 -                      $commentIDs[] = $comment->commentID;
 -              }
 -              
 -              if (!empty($groupCommentIDs)) {
 -                      $likeObjectIDs = [];
 -                      $notificationObjectTypes = [];
 -                      foreach ($groupCommentIDs as $objectTypeID => $objectIDs) {
 -                              // remove activity events
 -                              $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -                              if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.recentActivityEvent')) {
 -                                      UserActivityEventHandler::getInstance()->removeEvents($objectType->objectType.'.recentActivityEvent', $objectIDs);
 -                              }
 -                              
 -                              $likeObjectIDs = array_merge($likeObjectIDs, $objectIDs);
 -                              
 -                              // delete notifications
 -                              $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -                              if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
 -                                      UserNotificationHandler::getInstance()->removeNotifications($objectType->objectType.'.notification', $objectIDs);
 -                              }
 -                              
 -                              if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.like.notification')) {
 -                                      $notificationObjectTypes[] = $objectType->objectType.'.like.notification';
 -                              }
 -                      }
 -                      
 -                      // remove likes
 -                      ReactionHandler::getInstance()->removeReactions('com.woltlab.wcf.comment', $likeObjectIDs, $notificationObjectTypes);
 -              }
 -              
 -              if (!empty($commentIDs)) {
 -                      // delete responses
 -                      $commentResponseList = new CommentResponseList();
 -                      $commentResponseList->getConditionBuilder()->add('comment_response.commentID IN (?)', [$commentIDs]);
 -                      $commentResponseList->readObjectIDs();
 -                      if (count($commentResponseList->getObjectIDs())) {
 -                              $action = new CommentResponseAction($commentResponseList->getObjectIDs(), 'delete', [
 -                                      'ignoreCounters' => true
 -                              ]);
 -                              $action->executeAction();
 -                      }
 -                      
 -                      ModerationQueueManager::getInstance()->removeQueues(
 -                              'com.woltlab.wcf.comment.comment',
 -                              $commentIDs
 -                      );
 -              }
 -              
 -              return parent::delete();
 -      }
 -      
 -      /**
 -       * Validates parameters to load comments.
 -       * 
 -       * @throws      PermissionDeniedException
 -       */
 -      public function validateLoadComments() {
 -              $this->readInteger('lastCommentTime', false, 'data');
 -              $this->readInteger('objectID', false, 'data');
 -              
 -              $objectType = $this->validateObjectType();
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Returns parsed comments.
 -       * 
 -       * @return      array
 -       */
 -      public function loadComments() {
 -              $commentList = CommentHandler::getInstance()->getCommentList($this->commentProcessor, $this->parameters['data']['objectTypeID'], $this->parameters['data']['objectID'], false);
 -              $commentList->getConditionBuilder()->add("comment.time < ?", [$this->parameters['data']['lastCommentTime']]);
 -              $commentList->readObjects();
 -              
 -              // mark notifications for loaded comments as read
 -              CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
 -                      CommentHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID'])->objectType,
 -                      $commentList->getObjects()
 -              );
 -              
 -              WCF::getTPL()->assign([
 -                      'commentList' => $commentList,
 -                      'likeData' => MODULE_LIKE ? $commentList->getLikeData() : []
 -              ]);
 -              
 -              return [
 -                      'lastCommentTime' => $commentList->getMinCommentTime(),
 -                      'template' => WCF::getTPL()->fetch('commentList')
 -              ];
 -      }
 -      
 -      /**
 -       * Validates the `loadComment` action.
 -       * 
 -       * @throws      PermissionDeniedException
 -       * @since       3.1
 -       */
 -      public function validateLoadComment() {
 -              $this->readInteger('objectID', false, 'data');
 -              $this->readInteger('responseID', true, 'data');
 -              
 -              try {
 -                      $this->comment = $this->getSingleObject()->getDecoratedObject();
 -              }
 -              catch (UserInputException $e) {
 -                      /* unknown comment id, error handling takes place in `loadComment()` */
 -              }
 -              
 -              $objectType = $this->validateObjectType();
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              if (!empty($this->parameters['data']['responseID'])) {
 -                      $this->response = new CommentResponse($this->parameters['data']['responseID']);
 -                      if (!$this->response->responseID) {
 -                              $this->response = null;
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Returns a rendered comment.
 -       * 
 -       * @return      string[]
 -       * @since       3.1
 -       */
 -      public function loadComment() {
 -              if ($this->comment === null) {
 -                      return ['template' => ''];
 -              }
 -              else if ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
 -                      return ['template' => ''];
 -              }
 -              
 -              // mark notifications for loaded comment/response as read
 -              $objectType = CommentHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID'])->objectType;
 -              if ($this->response === null) {
 -                      CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
 -                              $objectType,
 -                              [new StructuredComment($this->comment)]
 -                      );
 -              }
 -              else {
 -                      CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
 -                              $objectType,
 -                              [$this->response]
 -                      );
 -              }
 -              
 -              $returnValues = $this->renderComment($this->comment, $this->response);
 -              return (is_array($returnValues)) ? $returnValues : ['template' => $returnValues];
 -      }
 -      
 -      /**
 -       * Validates the `loadResponse` action.
 -       * 
 -       * @since       3.1
 -       */
 -      public function validateLoadResponse() {
 -              $this->validateLoadComment();
 -      }
 -      
 -      /**
 -       * Returns a rendered comment.
 -       * 
 -       * @return      string[]
 -       * @since       3.1
 -       */
 -      public function loadResponse() {
 -              if ($this->comment === null || $this->response === null) {
 -                      return ['template' => ''];
 -              }
 -              else if ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
 -                      return ['template' => ''];
 -              }
 -              
 -              return [
 -                      'template' => $this->renderResponse($this->response)
 -              ];
 -      }
 -      
 -      /**
 -       * Validates parameters to add a comment.
 -       * 
 -       * @throws      PermissionDeniedException
 -       */
 -      public function validateAddComment() {
 -              CommentHandler::enforceFloodControl();
 -              
 -              $this->readInteger('objectID', false, 'data');
 -              $this->readBoolean('requireGuestDialog', true);
 -              
 -              if (!$this->parameters['requireGuestDialog']) {
 -                      $this->validateUsername();
 -                      $this->validateCaptcha();
 -              }
 -              
 -              $this->validateMessage();
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Adds a comment.
 -       * 
 -       * @return      string[]
 -       */
 -      public function addComment() {
 -              if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
 -                      if (!empty($this->validationErrors)) {
 -                              if (!empty($this->parameters['data']['username'])) {
 -                                      WCF::getSession()->register('username', $this->parameters['data']['username']);
 -                              }
 -                              WCF::getTPL()->assign('errorType', $this->validationErrors);
 -                      }
 -                      
 -                      $guestDialog = $this->getGuestDialog();
 -                      return [
 -                              'useCaptcha' => $guestDialog['useCaptcha'],
 -                              'guestDialog' => $guestDialog['template']
 -                      ];
 -              }
 -              
 -              /** @var HtmlInputProcessor $htmlInputProcessor */
 -              $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 -              
 -              // create comment
 -              $this->createdComment = CommentEditor::create([
 -                      'objectTypeID' => $this->parameters['data']['objectTypeID'],
 -                      'objectID' => $this->parameters['data']['objectID'],
 -                      'time' => TIME_NOW,
 -                      'userID' => WCF::getUser()->userID ?: null,
 -                      'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
 -                      'message' => $htmlInputProcessor->getHtml(),
 -                      'responses' => 0,
 -                      'responseIDs' => serialize([]),
 -                      'enableHtml' => 1,
 -                      'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1
 -              ]);
 -              
 -              if (!$this->createdComment->isDisabled) {
 -                      $action = new CommentAction([$this->createdComment], 'triggerPublication', [
 -                              'commentProcessor' => $this->commentProcessor
 -                      ]);
 -                      $action->executeAction();
 -              }
 -              else {
 -                      // mark comment for moderated content
 -                      ModerationQueueActivationManager::getInstance()->addModeratedContent('com.woltlab.wcf.comment.comment', $this->createdComment->commentID);
 -              }
 -              
 -              if (!$this->createdComment->userID) {
 -                      // save user name is session
 -                      WCF::getSession()->register('username', $this->createdComment->username);
 -                      
 -                      // save last comment time for flood control
 -                      WCF::getSession()->register('lastCommentTime', $this->createdComment->time);
 -                      
 -                      // reset captcha for future requests
 -                      if ($this->captchaObjectType) {
 -                              $this->captchaObjectType->getProcessor()->reset();
 -                      }
 -              }
 -              
 -              return [
 -                      'template' => $this->renderComment($this->createdComment)
 -              ];
 -      }
 -      
 -      public function triggerPublication() {
 -              if (!empty($this->parameters['commentProcessor'])) {
 -                      $objectType = null;
 -                      if (!empty($this->objects)) {
 -                              /** @var Comment $comment */
 -                              $comment = reset($this->objects);
 -                              $objectType = $this->validateObjectType($comment->objectTypeID);
 -                      }
 -                      
 -                      $this->commentProcessor = $this->parameters['commentProcessor'];
 -              }
 -              else {
 -                      $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
 -                      $this->commentProcessor = $objectType->getProcessor();
 -              }
 -              
 -              /** @var CommentEditor $comment */
 -              foreach ($this->objects as $comment) {
 -                      // update counter
 -                      $comment->update(['isDisabled' => 0]);
 -                      $this->commentProcessor->updateCounter($comment->objectID, 1);
 -                      
 -                      // fire activity event
 -                      if ($comment->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.recentActivityEvent')) {
 -                              UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType . '.recentActivityEvent', $comment->commentID, null, $comment->userID, $comment->time);
 -                      }
 -                      
 -                      // fire notification event
 -                      if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
 -                              $notificationObject = new CommentUserNotificationObject($comment->getDecoratedObject());
 -                              $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType . '.notification');
 -                              
 -                              if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
 -                                      $recipientIDs = $notificationObjectType->getRecipientIDs($comment->getDecoratedObject());
 -                                      
 -                                      // make sure that the active user gets no notification
 -                                      $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
 -                                      
 -                                      if (!empty($recipientIDs)) {
 -                                              UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType . '.notification', $notificationObject, $recipientIDs);
 -                                      }
 -                              }
 -                              else {
 -                                      /** @var ICommentUserNotificationObjectType $notificationObjectType */
 -                                      
 -                                      $userID = $notificationObjectType->getOwnerID($comment->commentID);
 -                                      if ($userID != WCF::getUser()->userID) {
 -                                              UserNotificationHandler::getInstance()->fireEvent('comment', $objectType->objectType . '.notification', $notificationObject, [$userID], ['objectUserID' => $userID]);
 -                                      }
 -                              }
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Validates parameters to add a response.
 -       * 
 -       * @throws      PermissionDeniedException
 -       */
 -      public function validateAddResponse() {
 -              CommentHandler::enforceFloodControl();
 -              
 -              $this->readInteger('objectID', false, 'data');
 -              $this->readBoolean('requireGuestDialog', true);
 -              
 -              if (!$this->parameters['requireGuestDialog']) {
 -                      $this->validateUsername();
 -                      $this->validateCaptcha();
 -              }
 -              
 -              $this->validateMessage();
 -              
 -              // validate comment id
 -              $this->validateCommentID();
 -              
 -              // disallow responses on disabled comments
 -              if ($this->comment->isDisabled) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Adds a response.
 -       * 
 -       * @return      array
 -       */
 -      public function addResponse() {
 -              if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
 -                      if (!empty($this->parameters['data']['username'])) {
 -                              WCF::getSession()->register('username', $this->parameters['data']['username']);
 -                      }
 -                      WCF::getTPL()->assign('errorType', $this->validationErrors);
 -                      
 -                      $guestDialog = $this->getGuestDialog();
 -                      return [
 -                              'useCaptcha' => $guestDialog['useCaptcha'],
 -                              'guestDialog' => $guestDialog['template']
 -                      ];
 -              }
 -              
 -              /** @var HtmlInputProcessor $htmlInputProcessor */
 -              $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 -              
 -              // create response
 -              $this->createdResponse = CommentResponseEditor::create([
 -                      'commentID' => $this->comment->commentID,
 -                      'time' => TIME_NOW,
 -                      'userID' => WCF::getUser()->userID ?: null,
 -                      'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
 -                      'message' => $htmlInputProcessor->getHtml(),
 -                      'enableHtml' => 1,
 -                      'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1
 -              ]);
 -              $this->createdResponse->setComment($this->comment);
 -              
 -              // update response data
 -              $unfilteredResponseIDs = $this->comment->getUnfilteredResponseIDs();
 -              if (count($unfilteredResponseIDs) < 5) {
 -                      $unfilteredResponseIDs[] = $this->createdResponse->responseID;
 -              }
 -              $unfilteredResponses = $this->comment->unfilteredResponses + 1;
 -              
 -              // update comment
 -              $commentEditor = new CommentEditor($this->comment);
 -              $commentEditor->update([
 -                      'unfilteredResponseIDs' => serialize($unfilteredResponseIDs),
 -                      'unfilteredResponses' => $unfilteredResponses
 -              ]);
 -              
 -              if (!$this->createdResponse->isDisabled) {
 -                      $action = new CommentAction([], 'triggerPublicationResponse', [
 -                              'commentProcessor' => $this->commentProcessor,
 -                              'responses' => [$this->createdResponse]
 -                      ]);
 -                      $action->executeAction();
 -              }
 -              else {
 -                      // mark response for moderated content
 -                      ModerationQueueActivationManager::getInstance()->addModeratedContent('com.woltlab.wcf.comment.response', $this->createdResponse->responseID);
 -              }
 -              
 -              if (!$this->createdResponse->userID) {
 -                      // save user name is session
 -                      WCF::getSession()->register('username', $this->createdResponse->username);
 -                      
 -                      // save last comment time for flood control
 -                      WCF::getSession()->register('lastCommentTime', $this->createdResponse->time);
 -                      
 -                      // reset captcha for future requests
 -                      if ($this->captchaObjectType) {
 -                              $this->captchaObjectType->getProcessor()->reset();
 -                      }
 -              }
 -              
 -              $responses = $this->comment->responses;
 -              if ($this->commentProcessor->canModerate($this->parameters['data']['objectTypeID'], $this->parameters['data']['objectID'])) {
 -                      $responses = $this->comment->unfilteredResponses;
 -              }
 -              
 -              return [
 -                      'commentID' => $this->comment->commentID,
 -                      'template' => $this->renderResponse($this->createdResponse),
 -                      'responses' => $responses + 1
 -              ];
 -      }
 -      
 -      /**
 -       * Publishes a response.
 -       */
 -      public function triggerPublicationResponse() {
 -              if (!empty($this->parameters['commentProcessor'])) {
 -                      $objectType = null;
 -                      if (!empty($this->parameters['responses'])) {
 -                              /** @var CommentResponse $response */
 -                              $response = reset($this->parameters['responses']);
 -                              $objectType = $this->validateObjectType($response->getComment()->objectTypeID);
 -                      }
 -                      
 -                      $this->commentProcessor = $this->parameters['commentProcessor'];
 -              }
 -              else {
 -                      $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
 -                      $this->commentProcessor = $objectType->getProcessor();
 -              }
 -              
 -              /** @var CommentResponse $response */
 -              foreach ($this->parameters['responses'] as $response) {
 -                      (new CommentResponseEditor($response))->update(['isDisabled' => 0]);
 -                      
 -                      $comment = $response->getComment();
 -                      
 -                      // update response count
 -                      $commentEditor = new CommentEditor($comment);
 -                      $commentEditor->updateCounters(['responses' => 1]);
 -                      
 -                      // do not prepend the response id as the approved response can appear anywhere
 -                      $commentEditor->updateResponseIDs();
 -                      
 -                      // update counter
 -                      $this->commentProcessor->updateCounter($comment->objectID, 1);
 -                      
 -                      // fire activity event
 -                      if ($response->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) {
 -                              UserActivityEventHandler::getInstance()->fireEvent($objectType->objectType.'.response.recentActivityEvent', $response->responseID, null, $response->userID, $response->time);
 -                      }
 -                      
 -                      // fire notification event
 -                      if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification') && UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
 -                              $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType.'.notification');
 -                              $notificationObject = new CommentResponseUserNotificationObject($response);
 -                              
 -                              if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
 -                                      $recipientIDs = $notificationObjectType->getRecipientIDs($comment);
 -                                      
 -                                      // make sure that the active user gets no notification
 -                                      $recipientIDs = array_diff($recipientIDs, [WCF::getUser()->userID]);
 -                                      
 -                                      if ($notificationObjectType instanceof IMultiRecipientCommentResponseOwnerUserNotificationObjectType) {
 -                                              $userID = $notificationObjectType->getCommentOwnerID($comment);
 -                                              if ($userID && $userID != WCF::getUser()->userID) {
 -                                                      UserNotificationHandler::getInstance()->fireEvent(
 -                                                              'commentResponseOwner',
 -                                                              $objectType->objectType . '.response.notification',
 -                                                              $notificationObject,
 -                                                              [$userID],
 -                                                              [
 -                                                                      'commentID' => $comment->commentID,
 -                                                                      'objectID' => $comment->objectID,
 -                                                                      'objectUserID' => $userID,
 -                                                                      'userID' => $comment->userID
 -                                                              ]
 -                                                      );
 -                                                      
 -                                                      $recipientIDs = array_diff($recipientIDs, [$userID]);
 -                                              }
 -                                      }
 -                                      
 -                                      if (!empty($recipientIDs)) {
 -                                              UserNotificationHandler::getInstance()->fireEvent(
 -                                                      'commentResponse',
 -                                                      $objectType->objectType . '.response.notification',
 -                                                      $notificationObject,
 -                                                      $recipientIDs,
 -                                                      [
 -                                                              'commentID' => $comment->commentID,
 -                                                              'objectID' => $comment->objectID,
 -                                                              'userID' => $comment->userID
 -                                                      ]
 -                                              );
 -                                      }
 -                              }
 -                              else {
 -                                      /** @var ICommentUserNotificationObjectType $notificationObjectType */
 -                                      $userID = $notificationObjectType->getOwnerID($comment->commentID);
 -                                      
 -                                      if ($comment->userID != WCF::getUser()->userID) {
 -                                              UserNotificationHandler::getInstance()->fireEvent(
 -                                                      'commentResponse',
 -                                                      $objectType->objectType . '.response.notification',
 -                                                      $notificationObject,
 -                                                      [$comment->userID],
 -                                                      [
 -                                                              'commentID' => $comment->commentID,
 -                                                              'objectID' => $comment->objectID,
 -                                                              'objectUserID' => $userID,
 -                                                              'userID' => $comment->userID
 -                                                      ]
 -                                              );
 -                                      }
 -                                      
 -                                      // notify the container owner
 -                                      if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.notification')) {
 -                                              if ($userID != $comment->userID && $userID != WCF::getUser()->userID) {
 -                                                      UserNotificationHandler::getInstance()->fireEvent(
 -                                                              'commentResponseOwner',
 -                                                              $objectType->objectType . '.response.notification',
 -                                                              $notificationObject,
 -                                                              [$userID], 
 -                                                              [
 -                                                                      'commentID' => $comment->commentID,
 -                                                                      'objectID' => $comment->objectID,
 -                                                                      'objectUserID' => $userID,
 -                                                                      'userID' => $comment->userID
 -                                                              ]
 -                                                      );
 -                                              }
 -                                      }
 -                              }
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Validates the `enable` action.
 -       * 
 -       * @throws      PermissionDeniedException
 -       */
 -      public function validateEnable() {
 -              $this->comment = $this->getSingleObject()->getDecoratedObject();
 -              
 -              $objectType = $this->validateObjectType($this->comment->objectTypeID);
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Enables a comment.
 -       * 
 -       * @return      integer[]
 -       */
 -      public function enable() {
 -              if ($this->comment === null) $this->comment = reset($this->objects);
 -              
 -              if ($this->comment->isDisabled) {
 -                      $action = new CommentAction([$this->comment], 'triggerPublication', [
 -                              'commentProcessor' => $this->commentProcessor,
 -                              'objectTypeID' => $this->comment->objectTypeID
 -                      ]);
 -                      $action->executeAction();
 -              }
 -              
 -              ModerationQueueActivationManager::getInstance()->removeModeratedContent('com.woltlab.wcf.comment.comment', [$this->comment->commentID]);
 -              
 -              return ['commentID' => $this->comment->commentID];
 -      }
 -      
 -      /**
 -       * Validates the `enableResponse` action.
 -       * 
 -       * @throws      PermissionDeniedException
 -       * @throws      UserInputException
 -       */
 -      public function validateEnableResponse() {
 -              $this->readInteger('responseID', false, 'data');
 -              $this->response = new CommentResponse($this->parameters['data']['responseID']);
 -              if (!$this->response->responseID) {
 -                      throw new UserInputException('responseID');
 -              }
 -              
 -              $this->comment = $this->response->getComment();
 -              
 -              $objectType = $this->validateObjectType($this->comment->objectTypeID);
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Enables a response.
 -       * 
 -       * @return      integer[]
 -       */
 -      public function enableResponse() {
 -              if ($this->comment === null) $this->comment = reset($this->objects);
 -              if ($this->response === null) $this->response = reset($this->parameters['responses']);
 -              
 -              if ($this->response->isDisabled) {
 -                      $action = new CommentAction([], 'triggerPublicationResponse', [
 -                              'commentProcessor' => $this->commentProcessor,
 -                              'objectTypeID' => $this->comment->objectTypeID,
 -                              'responses' => [$this->response]
 -                      ]);
 -                      $action->executeAction();
 -              }
 -              
 -              ModerationQueueActivationManager::getInstance()->removeModeratedContent('com.woltlab.wcf.comment.response', [$this->response->responseID]);
 -              
 -              return ['responseID' => $this->response->responseID];
 -      }
 -      
 -      /**
 -       * Validates parameters to edit a comment or a response.
 -       * 
 -       * @throws      PermissionDeniedException
 -       * @throws      UserInputException
 -       */
 -      public function validatePrepareEdit() {
 -              // validate response id
 -              try {
 -                      $this->validateResponseID();
 -              }
 -              catch (UserInputException $e) {
 -                      throw new UserInputException('objectIDs');
 -              }
 -              
 -              // validate object type id
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canEditResponse($this->response)) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Prepares editing of a comment or a response.
 -       * 
 -       * @return      array
 -       */
 -      public function prepareEdit() {
 -              return [
 -                      'action' => 'prepare',
 -                      'message' => $this->response->message,
 -                      'responseID' => $this->response->responseID
 -              ];
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateEdit() {
 -              $this->validatePrepareEdit();
 -              
 -              $this->validateMessage();
 -      }
 -      
 -      /**
 -       * Edits a comment or response.
 -       * 
 -       * @return      array
 -       */
 -      public function edit() {
 -              $editor = new CommentResponseEditor($this->response);
 -              $editor->update([
 -                      'message' => $this->parameters['data']['message']
 -              ]);
 -              $response = new CommentResponse($this->response->responseID);
 -              
 -              return [
 -                      'action' => 'saved',
 -                      'message' => $response->getFormattedMessage(),
 -                      'responseID' => $this->response->responseID
 -              ];
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateBeginEdit() {
 -              $this->comment = $this->getSingleObject();
 -              
 -              // validate object type id
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canEditComment($this->comment->getDecoratedObject())) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              $this->setDisallowedBBCodes();
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function beginEdit() {
 -              WCF::getTPL()->assign([
 -                      'comment' => $this->comment,
 -                      'wysiwygSelector' => 'commentEditor'.$this->comment->commentID
 -              ]);
 -              
 -              return [
 -                      'actionName' => 'beginEdit',
 -                      'template' => WCF::getTPL()->fetch('commentEditor', 'wcf')
 -              ];
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateSave() {
 -              $this->validateBeginEdit();
 -              
 -              $this->validateMessage();
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function save() {
 -              /** @var HtmlInputProcessor $htmlInputProcessor */
 -              $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 -              
 -              $action = new CommentAction([$this->comment], 'update', [
 -                      'data' => [
 -                              'message' => $htmlInputProcessor->getHtml()
 -                      ]
 -              ]);
 -              $action->executeAction();
 -              
 -              return [
 -                      'actionName' => 'save',
 -                      'message' => (new Comment($this->comment->commentID))->getFormattedMessage()
 -              ];
 -      }
 -      
 -      /**
 -       * Validates parameters to remove a comment or response.
 -       * 
 -       * @throws      PermissionDeniedException
 -       * @throws      UserInputException
 -       */
 -      public function validateRemove() {
 -              // validate comment id or response id
 -              try {
 -                      $this->validateCommentID();
 -              }
 -              catch (UserInputException $e) {
 -                      try {
 -                              $this->validateResponseID();
 -                      }
 -                      catch (UserInputException $e) {
 -                              throw new UserInputException('objectIDs');
 -                      }
 -              }
 -              
 -              // validate object type id
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if ($this->comment !== null) {
 -                      if (!$this->commentProcessor->canDeleteComment($this->comment)) {
 -                              throw new PermissionDeniedException();
 -                      }
 -              }
 -              else {
 -                      if (!$this->commentProcessor->canDeleteResponse($this->response)) {
 -                              throw new PermissionDeniedException();
 -                      }
 -              }
 -      }
 -      
 -      /**
 -       * Removes a comment or response.
 -       * 
 -       * @return      integer[]
 -       */
 -      public function remove() {
 -              if ($this->comment !== null) {
 -                      $objectAction = new CommentAction([$this->comment], 'delete');
 -                      $objectAction->executeAction();
 -                      
 -                      return ['commentID' => $this->comment->commentID];
 -              }
 -              else {
 -                      $objectAction = new CommentResponseAction([$this->response], 'delete');
 -                      $objectAction->executeAction();
 -                      
 -                      return ['responseID' => $this->response->responseID];
 -              }
 -      }
 -      
 -      /**
 -       * Validates the 'getGuestDialog' action.
 -       * 
 -       * @throws      PermissionDeniedException
 -       */
 -      public function validateGetGuestDialog() {
 -              if (WCF::getUser()->userID) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              CommentHandler::enforceFloodControl();
 -              
 -              $this->readInteger('objectID', false, 'data');
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              // validate message already at this point to make sure that the
 -              // message is valid when submitting the dialog to avoid having to
 -              // go back to the message to fix it
 -              $this->validateMessage();
 -      }
 -      
 -      /**
 -       * Returns the dialog for guests when they try to write a comment letting
 -       * them enter a username and solving a captcha.
 -       * 
 -       * @return      array
 -       * @throws      SystemException
 -       */
 -      public function getGuestDialog() {
 -              $captchaObjectType = null;
 -              
 -              if (CAPTCHA_TYPE) {
 -                      $captchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName(CAPTCHA_TYPE);
 -                      if ($captchaObjectType === null) {
 -                              throw new SystemException("Unknown captcha object type with name '".CAPTCHA_TYPE."'");
 -                      }
 -                      
 -                      if (!$captchaObjectType->getProcessor()->isAvailable()) {
 -                              $captchaObjectType = null;
 -                      }
 -              }
 -              
 -              return [
 -                      'useCaptcha' => $captchaObjectType !== null,
 -                      'template' => WCF::getTPL()->fetch('commentAddGuestDialog', 'wcf', [
 -                              'ajaxCaptcha' => true,
 -                              'captchaID' => 'commentAdd',
 -                              'captchaObjectType' => $captchaObjectType,
 -                              'supportsAsyncCaptcha' => true,
 -                              'username' => WCF::getSession()->getVar('username')
 -                      ])
 -              ];
 -      }
 -      
 -      /**
 -       * Renders a comment.
 -       * 
 -       * @param       Comment         $comment
 -       * @param       CommentResponse $response
 -       * @return      string|string[]
 -       */
 -      protected function renderComment(Comment $comment, CommentResponse $response = null) {
 -              $comment = new StructuredComment($comment);
 -              $comment->setIsDeletable($this->commentProcessor->canDeleteComment($comment->getDecoratedObject()));
 -              $comment->setIsEditable($this->commentProcessor->canEditComment($comment->getDecoratedObject()));
 -              
 -              if ($response !== null) {
 -                      // check if response is not visible
 -                      /** @var CommentResponse $visibleResponse */
 -                      foreach ($comment as $visibleResponse) {
 -                              if ($visibleResponse->responseID == $response->responseID) {
 -                                      $response = null;
 -                                      break;
 -                              }
 -                      }
 -              }
 -              
 -              // load last response time
 -              if ($comment->getDecoratedObject()->responses) {
 -                      $sql = "SELECT          time
 -                              FROM            wcf".WCF_N."_comment_response
 -                              WHERE           commentID = ?
 -                              ORDER BY        time";
 -                      $statement = WCF::getDB()->prepareStatement($sql, 1);
 -                      $statement->execute([$comment->commentID]);
 -                      $lastResponseTime = $statement->fetchSingleColumn();
 -                      if ($lastResponseTime && $lastResponseTime > 1) {
 -                              WCF::getTPL()->assign('commentLastResponseTime', ($lastResponseTime - 1));
 -                      }
 -              }
 -              
 -              WCF::getTPL()->assign([
 -                      'commentCanModerate' => $this->commentProcessor->canModerate($comment->getDecoratedObject()->objectTypeID, $comment->getDecoratedObject()->objectID),
 -                      'commentList' => [$comment],
 -                      'commentManager' => $this->commentProcessor
 -              ]);
 -              
 -              // load like data
 -              if (MODULE_LIKE) {
 -                      $likeData = [];
 -                      $commentObjectType = ReactionHandler::getInstance()->getObjectType('com.woltlab.wcf.comment');
 -                      ReactionHandler::getInstance()->loadLikeObjects($commentObjectType, [$comment->commentID]);
 -                      $likeData['comment'] = ReactionHandler::getInstance()->getLikeObjects($commentObjectType);
 -                      
 -                      $responseIDs = [];
 -                      foreach ($comment as $visibleResponse) {
 -                              $responseIDs[] = $visibleResponse->responseID;
 -                      }
 -                      
 -                      if ($response !== null) {
 -                              $responseIDs[] = $response->responseID;
 -                      }
 -                      
 -                      if (!empty($responseIDs)) {
 -                              $responseObjectType = ReactionHandler::getInstance()->getObjectType('com.woltlab.wcf.comment.response');
 -                              ReactionHandler::getInstance()->loadLikeObjects($responseObjectType, $responseIDs);
 -                              $likeData['response'] = ReactionHandler::getInstance()->getLikeObjects($responseObjectType);
 -                      }
 -                      
 -                      WCF::getTPL()->assign('likeData', $likeData);
 -              }
 -              
 -              $template = WCF::getTPL()->fetch('commentList');
 -              if ($response === null) {
 -                      return $template;
 -              }
 -              
 -              return [
 -                      'template' => $template,
 -                      'response' => $this->renderResponse($response)
 -              ];
 -      }
 -      
 -      /**
 -       * Renders a response.
 -       * 
 -       * @param       CommentResponse $response
 -       * @return      string
 -       */
 -      protected function renderResponse(CommentResponse $response) {
 -              $response = new StructuredCommentResponse($response);
 -              $response->setIsDeletable($this->commentProcessor->canDeleteResponse($response->getDecoratedObject()));
 -              $response->setIsEditable($this->commentProcessor->canEditResponse($response->getDecoratedObject()));
 -              
 -              // render response
 -              WCF::getTPL()->assign([
 -                      'responseList' => [$response],
 -                      'commentCanModerate' => $this->commentProcessor->canModerate($response->getComment()->objectTypeID, $response->getComment()->objectID),
 -                      'commentManager' => $this->commentProcessor
 -              ]);
 -              return WCF::getTPL()->fetch('commentResponseList');
 -      }
 -      
 -      /**
 -       * Validates message parameters.
 -       * 
 -       * @throws      UserInputException
 -       */
 -      protected function validateMessage() {
 -              $this->readString('message', false, 'data');
 -              $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
 -              
 -              if (empty($this->parameters['data']['message'])) {
 -                      throw new UserInputException('message');
 -              }
 -              
 -              CommentHandler::enforceCensorship($this->parameters['data']['message']);
 -              
 -              $this->setDisallowedBBCodes();
 -              $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message'], ($this->comment !== null ? $this->comment->commentID : 0));
 -              
 -              // search for disallowed bbcodes
 -              $disallowedBBCodes = $htmlInputProcessor->validate();
 -              if (!empty($disallowedBBCodes)) {
 -                      throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
 -              }
 -              
 -              if ($htmlInputProcessor->appearsToBeEmpty()) {
 -                      throw new UserInputException('message');
 -              }
 -              
 -              $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
 -      }
 -      
 -      /**
 -       * Validates object type id parameter.
 -       * 
 -       * @param       integer         $objectTypeID
 -       * @return      ObjectType
 -       * @throws      UserInputException
 -       */
 -      protected function validateObjectType($objectTypeID = null) {
 -              if ($objectTypeID === null) {
 -                      $this->readInteger('objectTypeID', false, 'data');
 -                      $objectTypeID = $this->parameters['data']['objectTypeID'];
 -              }
 -              
 -              $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -              if ($objectType === null) {
 -                      throw new UserInputException('objectTypeID');
 -              }
 -              
 -              return $objectType;
 -      }
 -      
 -      /**
 -       * Validates comment id parameter.
 -       * 
 -       * @throws      UserInputException
 -       */
 -      protected function validateCommentID() {
 -              $this->readInteger('commentID', false, 'data');
 -              
 -              $this->comment = new Comment($this->parameters['data']['commentID']);
 -              if ($this->comment === null || !$this->comment->commentID) {
 -                      throw new UserInputException('commentID');
 -              }
 -      }
 -      
 -      /**
 -       * Validates response id parameter.
 -       * 
 -       * @throws      UserInputException
 -       */
 -      protected function validateResponseID() {
 -              if (isset($this->parameters['data']['responseID'])) {
 -                      $this->response = new CommentResponse($this->parameters['data']['responseID']);
 -              }
 -              if ($this->response === null || !$this->response->responseID) {
 -                      throw new UserInputException('responseID');
 -              }
 -      }
 -      
 -      /**
 -       * Validates the username parameter.
 -       */
 -      protected function validateUsername() {
 -              if (WCF::getUser()->userID) return;
 -              
 -              try {
 -                      $this->readString('username', false, 'data');
 -                      
 -                      if (!UserRegistrationUtil::isValidUsername($this->parameters['data']['username'])) {
 -                              throw new UserInputException('username', 'invalid');
 -                      }
 -                      if (!UserUtil::isAvailableUsername($this->parameters['data']['username'])) {
 -                              throw new UserInputException('username', 'notUnique');
 -                      }
 -              }
 -              catch (UserInputException $e) {
 -                      $this->validationErrors['username'] = $e->getType();
 -              }
 -      }
 -      
 -      /**
 -       * Validates the captcha challenge.
 -       * 
 -       * @throws      SystemException
 -       */
 -      protected function validateCaptcha() {
 -              if (WCF::getUser()->userID) return;
 -              
 -              if (CAPTCHA_TYPE) {
 -                      $this->captchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName(CAPTCHA_TYPE);
 -                      if ($this->captchaObjectType === null) {
 -                              throw new SystemException("Unknown captcha object type with name '".CAPTCHA_TYPE."'");
 -                      }
 -                      
 -                      if (!$this->captchaObjectType->getProcessor()->isAvailable()) {
 -                              $this->captchaObjectType = null;
 -                      }
 -              }
 -              
 -              if ($this->captchaObjectType === null) return;
 -              
 -              try {
 -                      $this->captchaObjectType->getProcessor()->readFormParameters();
 -                      $this->captchaObjectType->getProcessor()->validate();
 -              }
 -              catch (UserInputException $e) {
 -                      $this->validationErrors = array_merge($this->validationErrors, [$e->getField() => $e->getType()]);
 -              }
 -      }
 -      
 -      /**
 -       * Sets the list of disallowed bbcodes for comments.
 -       */
 -      protected function setDisallowedBBCodes() {
 -              BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
 -      }
 -      
 -      /**
 -       * Returns the current html input processor or a new one if `$message` is not null.
 -       * 
 -       * @param       string|null     $message        source message
 -       * @param       integer         $objectID       object id
 -       * @return      HtmlInputProcessor
 -       */
 -      public function getHtmlInputProcessor($message = null, $objectID = 0) {
 -              if ($message === null) {
 -                      return $this->htmlInputProcessor;
 -              }
 -              
 -              $this->htmlInputProcessor = new HtmlInputProcessor();
 -              $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
 -              
 -              return $this->htmlInputProcessor;
 -      }
 -      
 -      /**
 -       * Returns the comment object.
 -       * 
 -       * @return      Comment
 -       */
 -      public function getComment() {
 -              return $this->comment;
 -      }
 -      
 -      /**
 -       * Returns the comment response object.
 -       * 
 -       * @return      CommentResponse
 -       */
 -      public function getResponse() {
 -              return $this->response;
 -      }
 -      
 -      /**
 -       * Returns the comment manager.
 -       * 
 -       * @return      ICommentManager
 -       */
 -      public function getCommentManager() {
 -              return $this->commentProcessor;
 -      }
 +class CommentAction extends AbstractDatabaseObjectAction implements IMessageInlineEditorAction
 +{
 +    /**
 +     * @inheritDoc
 +     */
 +    protected $allowGuestAccess = [
 +        'addComment',
 +        'addResponse',
 +        'loadComment',
 +        'loadComments',
 +        'loadResponse',
 +        'getGuestDialog',
 +    ];
 +
 +    /**
 +     * captcha object type used for comments
 +     * @var ObjectType
 +     */
 +    public $captchaObjectType;
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    protected $className = CommentEditor::class;
 +
 +    /**
 +     * comment object
 +     * @var Comment
 +     */
 +    protected $comment;
 +
 +    /**
 +     * comment processor
 +     * @var ICommentManager
 +     */
 +    protected $commentProcessor;
 +
 +    /**
 +     * @var HtmlInputProcessor
 +     */
 +    protected $htmlInputProcessor;
 +
 +    /**
 +     * response object
 +     * @var CommentResponse
 +     */
 +    protected $response;
 +
 +    /**
 +     * comment object created by addComment()
 +     * @var Comment
 +     */
 +    public $createdComment;
 +
 +    /**
 +     * response object created by addResponse()
 +     * @var CommentResponse
 +     */
 +    public $createdResponse;
 +
 +    /**
 +     * errors occurring through the validation of addComment or addResponse
 +     * @var array
 +     */
 +    public $validationErrors = [];
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function delete()
 +    {
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +        }
 +
 +        // update counters
 +        /** @var ICommentManager[] $processors */
 +        $processors = [];
 +        $groupCommentIDs = $commentIDs = [];
 +        foreach ($this->getObjects() as $comment) {
 +            if (!isset($processors[$comment->objectTypeID])) {
 +                $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
 +                $processors[$comment->objectTypeID] = $objectType->getProcessor();
 +
 +                $groupCommentIDs[$comment->objectTypeID] = [];
 +            }
 +
 +            if (!$comment->isDisabled) {
 +                $processors[$comment->objectTypeID]->updateCounter($comment->objectID, -1 * ($comment->responses + 1));
 +            }
 +
 +            $groupCommentIDs[$comment->objectTypeID][] = $comment->commentID;
 +            $commentIDs[] = $comment->commentID;
 +        }
 +
 +        if (!empty($groupCommentIDs)) {
 +            $likeObjectIDs = [];
 +            $notificationObjectTypes = [];
 +            foreach ($groupCommentIDs as $objectTypeID => $objectIDs) {
 +                // remove activity events
 +                $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +                if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.recentActivityEvent')) {
 +                    UserActivityEventHandler::getInstance()->removeEvents(
 +                        $objectType->objectType . '.recentActivityEvent',
 +                        $objectIDs
 +                    );
 +                }
 +
 +                $likeObjectIDs = \array_merge($likeObjectIDs, $objectIDs);
 +
 +                // delete notifications
 +                $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +                if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
 +                    UserNotificationHandler::getInstance()->removeNotifications(
 +                        $objectType->objectType . '.notification',
 +                        $objectIDs
 +                    );
 +                }
 +
 +                if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.like.notification')) {
 +                    $notificationObjectTypes[] = $objectType->objectType . '.like.notification';
 +                }
 +            }
 +
 +            // remove likes
 +            ReactionHandler::getInstance()->removeReactions(
 +                'com.woltlab.wcf.comment',
 +                $likeObjectIDs,
 +                $notificationObjectTypes
 +            );
 +        }
 +
-         // delete responses
 +        if (!empty($commentIDs)) {
++            // delete responses
 +            $commentResponseList = new CommentResponseList();
 +            $commentResponseList->getConditionBuilder()->add('comment_response.commentID IN (?)', [$commentIDs]);
 +            $commentResponseList->readObjectIDs();
 +            if (\count($commentResponseList->getObjectIDs())) {
 +                $action = new CommentResponseAction($commentResponseList->getObjectIDs(), 'delete', [
 +                    'ignoreCounters' => true,
 +                ]);
 +                $action->executeAction();
 +            }
++
++            ModerationQueueManager::getInstance()->removeQueues(
++                'com.woltlab.wcf.comment.comment',
++                $commentIDs
++            );
 +        }
 +
 +        return parent::delete();
 +    }
 +
 +    /**
 +     * Validates parameters to load comments.
 +     *
 +     * @throws  PermissionDeniedException
 +     */
 +    public function validateLoadComments()
 +    {
 +        $this->readInteger('lastCommentTime', false, 'data');
 +        $this->readInteger('objectID', false, 'data');
 +
 +        $objectType = $this->validateObjectType();
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Returns parsed comments.
 +     *
 +     * @return  array
 +     */
 +    public function loadComments()
 +    {
 +        $commentList = CommentHandler::getInstance()->getCommentList(
 +            $this->commentProcessor,
 +            $this->parameters['data']['objectTypeID'],
 +            $this->parameters['data']['objectID'],
 +            false
 +        );
 +        $commentList->getConditionBuilder()->add("comment.time < ?", [$this->parameters['data']['lastCommentTime']]);
 +        $commentList->readObjects();
 +
 +        // mark notifications for loaded comments as read
 +        CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
 +            CommentHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID'])->objectType,
 +            $commentList->getObjects()
 +        );
 +
 +        WCF::getTPL()->assign([
 +            'commentList' => $commentList,
 +            'likeData' => MODULE_LIKE ? $commentList->getLikeData() : [],
 +        ]);
 +
 +        return [
 +            'lastCommentTime' => $commentList->getMinCommentTime(),
 +            'template' => WCF::getTPL()->fetch('commentList'),
 +        ];
 +    }
 +
 +    /**
 +     * Validates the `loadComment` action.
 +     *
 +     * @throws  PermissionDeniedException
 +     * @since   3.1
 +     */
 +    public function validateLoadComment()
 +    {
 +        $this->readInteger('objectID', false, 'data');
 +        $this->readInteger('responseID', true, 'data');
 +
 +        try {
 +            $this->comment = $this->getSingleObject()->getDecoratedObject();
 +        } catch (UserInputException $e) {
 +            /* unknown comment id, error handling takes place in `loadComment()` */
 +        }
 +
 +        $objectType = $this->validateObjectType();
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->isAccessible($this->parameters['data']['objectID'])) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        if (!empty($this->parameters['data']['responseID'])) {
 +            $this->response = new CommentResponse($this->parameters['data']['responseID']);
 +            if (!$this->response->responseID) {
 +                $this->response = null;
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Returns a rendered comment.
 +     *
 +     * @return  string[]
 +     * @since   3.1
 +     */
 +    public function loadComment()
 +    {
 +        if ($this->comment === null) {
 +            return ['template' => ''];
 +        } elseif ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
 +            return ['template' => ''];
 +        }
 +
 +        // mark notifications for loaded comment/response as read
 +        $objectType = CommentHandler::getInstance()->getObjectType($this->parameters['data']['objectTypeID'])->objectType;
 +        if ($this->response === null) {
 +            CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
 +                $objectType,
 +                [new StructuredComment($this->comment)]
 +            );
 +        } else {
 +            CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
 +                $objectType,
 +                [$this->response]
 +            );
 +        }
 +
 +        $returnValues = $this->renderComment($this->comment, $this->response);
 +
 +        return (\is_array($returnValues)) ? $returnValues : ['template' => $returnValues];
 +    }
 +
 +    /**
 +     * Validates the `loadResponse` action.
 +     *
 +     * @since   3.1
 +     */
 +    public function validateLoadResponse()
 +    {
 +        $this->validateLoadComment();
 +    }
 +
 +    /**
 +     * Returns a rendered comment.
 +     *
 +     * @return  string[]
 +     * @since   3.1
 +     */
 +    public function loadResponse()
 +    {
 +        if ($this->comment === null || $this->response === null) {
 +            return ['template' => ''];
 +        } elseif ($this->comment->objectTypeID != $this->parameters['data']['objectTypeID'] || $this->comment->objectID != $this->parameters['data']['objectID']) {
 +            return ['template' => ''];
 +        }
 +
 +        return [
 +            'template' => $this->renderResponse($this->response),
 +        ];
 +    }
 +
 +    /**
 +     * Validates parameters to add a comment.
 +     *
 +     * @throws  PermissionDeniedException
 +     */
 +    public function validateAddComment()
 +    {
 +        try {
 +            CommentHandler::enforceFloodControl();
 +        } catch (NamedUserException $e) {
 +            throw new UserInputException('message', $e->getMessage());
 +        }
 +
 +        $this->readInteger('objectID', false, 'data');
 +        $this->readBoolean('requireGuestDialog', true);
 +
 +        if (!$this->parameters['requireGuestDialog']) {
 +            $this->validateUsername();
 +            $this->validateCaptcha();
 +        }
 +
 +        $this->validateMessage();
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Adds a comment.
 +     *
 +     * @return  string[]
 +     */
 +    public function addComment()
 +    {
 +        if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
 +            if (!empty($this->validationErrors)) {
 +                if (!empty($this->parameters['data']['username'])) {
 +                    WCF::getSession()->register('username', $this->parameters['data']['username']);
 +                }
 +                WCF::getTPL()->assign('errorType', $this->validationErrors);
 +            }
 +
 +            $guestDialog = $this->getGuestDialog();
 +
 +            return [
 +                'useCaptcha' => $guestDialog['useCaptcha'],
 +                'guestDialog' => $guestDialog['template'],
 +            ];
 +        }
 +
 +        /** @var HtmlInputProcessor $htmlInputProcessor */
 +        $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 +
 +        // create comment
 +        $this->createdComment = CommentEditor::create([
 +            'objectTypeID' => $this->parameters['data']['objectTypeID'],
 +            'objectID' => $this->parameters['data']['objectID'],
 +            'time' => TIME_NOW,
 +            'userID' => WCF::getUser()->userID ?: null,
 +            'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
 +            'message' => $htmlInputProcessor->getHtml(),
 +            'responses' => 0,
 +            'responseIDs' => \serialize([]),
 +            'enableHtml' => 1,
 +            'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1,
 +        ]);
 +
 +        if (!$this->createdComment->isDisabled) {
 +            $action = new self([$this->createdComment], 'triggerPublication', [
 +                'commentProcessor' => $this->commentProcessor,
 +            ]);
 +            $action->executeAction();
 +        } else {
 +            // mark comment for moderated content
 +            ModerationQueueActivationManager::getInstance()->addModeratedContent(
 +                'com.woltlab.wcf.comment.comment',
 +                $this->createdComment->commentID
 +            );
 +        }
 +
 +        FloodControl::getInstance()->registerContent('com.woltlab.wcf.comment');
 +
 +        if (!$this->createdComment->userID) {
 +            // save user name is session
 +            WCF::getSession()->register('username', $this->createdComment->username);
 +
 +            // save last comment time for flood control
 +            WCF::getSession()->register('lastCommentTime', $this->createdComment->time);
 +
 +            // reset captcha for future requests
 +            if ($this->captchaObjectType) {
 +                $this->captchaObjectType->getProcessor()->reset();
 +            }
 +        }
 +
 +        return [
 +            'template' => $this->renderComment($this->createdComment),
 +        ];
 +    }
 +
 +    public function triggerPublication()
 +    {
 +        if (!empty($this->parameters['commentProcessor'])) {
 +            $objectType = null;
 +            if (!empty($this->objects)) {
 +                /** @var Comment $comment */
 +                $comment = \reset($this->objects);
 +                $objectType = $this->validateObjectType($comment->objectTypeID);
 +            }
 +
 +            $this->commentProcessor = $this->parameters['commentProcessor'];
 +        } else {
 +            $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
 +            $this->commentProcessor = $objectType->getProcessor();
 +        }
 +
 +        /** @var CommentEditor $comment */
 +        foreach ($this->objects as $comment) {
 +            // update counter
 +            $comment->update(['isDisabled' => 0]);
 +            $this->commentProcessor->updateCounter($comment->objectID, 1);
 +
 +            // fire activity event
 +            if ($comment->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.recentActivityEvent')) {
 +                UserActivityEventHandler::getInstance()->fireEvent(
 +                    $objectType->objectType . '.recentActivityEvent',
 +                    $comment->commentID,
 +                    null,
 +                    $comment->userID,
 +                    $comment->time
 +                );
 +            }
 +
 +            // fire notification event
 +            if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
 +                $notificationObject = new CommentUserNotificationObject($comment->getDecoratedObject());
 +                $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType . '.notification');
 +
 +                if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
 +                    $recipientIDs = $notificationObjectType->getRecipientIDs($comment->getDecoratedObject());
 +
 +                    // make sure that the active user gets no notification
 +                    $recipientIDs = \array_diff($recipientIDs, [WCF::getUser()->userID]);
 +
 +                    if (!empty($recipientIDs)) {
 +                        UserNotificationHandler::getInstance()->fireEvent(
 +                            'comment',
 +                            $objectType->objectType . '.notification',
 +                            $notificationObject,
 +                            $recipientIDs
 +                        );
 +                    }
 +                } else {
 +                    /** @var ICommentUserNotificationObjectType $notificationObjectType */
 +
 +                    $userID = $notificationObjectType->getOwnerID($comment->commentID);
 +                    if ($userID != WCF::getUser()->userID) {
 +                        UserNotificationHandler::getInstance()->fireEvent(
 +                            'comment',
 +                            $objectType->objectType . '.notification',
 +                            $notificationObject,
 +                            [$userID],
 +                            ['objectUserID' => $userID]
 +                        );
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Validates parameters to add a response.
 +     *
 +     * @throws  PermissionDeniedException
 +     */
 +    public function validateAddResponse()
 +    {
 +        try {
 +            CommentHandler::enforceFloodControl();
 +        } catch (NamedUserException $e) {
 +            throw new UserInputException('message', $e->getMessage());
 +        }
 +
 +        $this->readInteger('objectID', false, 'data');
 +        $this->readBoolean('requireGuestDialog', true);
 +
 +        if (!$this->parameters['requireGuestDialog']) {
 +            $this->validateUsername();
 +            $this->validateCaptcha();
 +        }
 +
 +        $this->validateMessage();
 +
 +        // validate comment id
 +        $this->validateCommentID();
 +
 +        // disallow responses on disabled comments
 +        if ($this->comment->isDisabled) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Adds a response.
 +     *
 +     * @return  array
 +     */
 +    public function addResponse()
 +    {
 +        if ($this->parameters['requireGuestDialog'] || !empty($this->validationErrors)) {
 +            if (!empty($this->parameters['data']['username'])) {
 +                WCF::getSession()->register('username', $this->parameters['data']['username']);
 +            }
 +            WCF::getTPL()->assign('errorType', $this->validationErrors);
 +
 +            $guestDialog = $this->getGuestDialog();
 +
 +            return [
 +                'useCaptcha' => $guestDialog['useCaptcha'],
 +                'guestDialog' => $guestDialog['template'],
 +            ];
 +        }
 +
 +        /** @var HtmlInputProcessor $htmlInputProcessor */
 +        $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 +
 +        // create response
 +        $this->createdResponse = CommentResponseEditor::create([
 +            'commentID' => $this->comment->commentID,
 +            'time' => TIME_NOW,
 +            'userID' => WCF::getUser()->userID ?: null,
 +            'username' => WCF::getUser()->userID ? WCF::getUser()->username : $this->parameters['data']['username'],
 +            'message' => $htmlInputProcessor->getHtml(),
 +            'enableHtml' => 1,
 +            'isDisabled' => $this->commentProcessor->canAddWithoutApproval($this->parameters['data']['objectID']) ? 0 : 1,
 +        ]);
 +        $this->createdResponse->setComment($this->comment);
 +
 +        // update response data
 +        $unfilteredResponseIDs = $this->comment->getUnfilteredResponseIDs();
 +        if (\count($unfilteredResponseIDs) < 5) {
 +            $unfilteredResponseIDs[] = $this->createdResponse->responseID;
 +        }
 +        $unfilteredResponses = $this->comment->unfilteredResponses + 1;
 +
 +        // update comment
 +        $commentEditor = new CommentEditor($this->comment);
 +        $commentEditor->update([
 +            'unfilteredResponseIDs' => \serialize($unfilteredResponseIDs),
 +            'unfilteredResponses' => $unfilteredResponses,
 +        ]);
 +
 +        if (!$this->createdResponse->isDisabled) {
 +            $action = new self([], 'triggerPublicationResponse', [
 +                'commentProcessor' => $this->commentProcessor,
 +                'responses' => [$this->createdResponse],
 +            ]);
 +            $action->executeAction();
 +        } else {
 +            // mark response for moderated content
 +            ModerationQueueActivationManager::getInstance()->addModeratedContent(
 +                'com.woltlab.wcf.comment.response',
 +                $this->createdResponse->responseID
 +            );
 +        }
 +
 +        FloodControl::getInstance()->registerContent('com.woltlab.wcf.comment');
 +
 +        if (!$this->createdResponse->userID) {
 +            // save user name is session
 +            WCF::getSession()->register('username', $this->createdResponse->username);
 +
 +            // save last comment time for flood control
 +            WCF::getSession()->register('lastCommentTime', $this->createdResponse->time);
 +
 +            // reset captcha for future requests
 +            if ($this->captchaObjectType) {
 +                $this->captchaObjectType->getProcessor()->reset();
 +            }
 +        }
 +
 +        $responses = $this->comment->responses;
 +        if (
 +            $this->commentProcessor->canModerate(
 +                $this->parameters['data']['objectTypeID'],
 +                $this->parameters['data']['objectID']
 +            )
 +        ) {
 +            $responses = $this->comment->unfilteredResponses;
 +        }
 +
 +        return [
 +            'commentID' => $this->comment->commentID,
 +            'template' => $this->renderResponse($this->createdResponse),
 +            'responses' => $responses + 1,
 +        ];
 +    }
 +
 +    /**
 +     * Publishes a response.
 +     */
 +    public function triggerPublicationResponse()
 +    {
 +        if (!empty($this->parameters['commentProcessor'])) {
 +            $objectType = null;
 +            if (!empty($this->parameters['responses'])) {
 +                /** @var CommentResponse $response */
 +                $response = \reset($this->parameters['responses']);
 +                $objectType = $this->validateObjectType($response->getComment()->objectTypeID);
 +            }
 +
 +            $this->commentProcessor = $this->parameters['commentProcessor'];
 +        } else {
 +            $objectType = $this->validateObjectType($this->parameters['objectTypeID']);
 +            $this->commentProcessor = $objectType->getProcessor();
 +        }
 +
 +        /** @var CommentResponse $response */
 +        foreach ($this->parameters['responses'] as $response) {
 +            (new CommentResponseEditor($response))->update(['isDisabled' => 0]);
 +
 +            $comment = $response->getComment();
 +
 +            // update response count
 +            $commentEditor = new CommentEditor($comment);
 +            $commentEditor->updateCounters(['responses' => 1]);
 +
 +            // do not prepend the response id as the approved response can appear anywhere
 +            $commentEditor->updateResponseIDs();
 +
 +            // update counter
 +            $this->commentProcessor->updateCounter($comment->objectID, 1);
 +
 +            // fire activity event
 +            if ($response->userID && UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.response.recentActivityEvent')) {
 +                UserActivityEventHandler::getInstance()->fireEvent(
 +                    $objectType->objectType . '.response.recentActivityEvent',
 +                    $response->responseID,
 +                    null,
 +                    $response->userID,
 +                    $response->time
 +                );
 +            }
 +
 +            // fire notification event
 +            if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.response.notification') && UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
 +                $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($objectType->objectType . '.notification');
 +                $notificationObject = new CommentResponseUserNotificationObject($response);
 +
 +                if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) {
 +                    $recipientIDs = $notificationObjectType->getRecipientIDs($comment);
 +
 +                    // make sure that the active user gets no notification
 +                    $recipientIDs = \array_diff($recipientIDs, [WCF::getUser()->userID]);
 +
 +                    if ($notificationObjectType instanceof IMultiRecipientCommentResponseOwnerUserNotificationObjectType) {
 +                        $userID = $notificationObjectType->getCommentOwnerID($comment);
 +                        if ($userID && $userID != WCF::getUser()->userID) {
 +                            UserNotificationHandler::getInstance()->fireEvent(
 +                                'commentResponseOwner',
 +                                $objectType->objectType . '.response.notification',
 +                                $notificationObject,
 +                                [$userID],
 +                                [
 +                                    'commentID' => $comment->commentID,
 +                                    'objectID' => $comment->objectID,
 +                                    'objectUserID' => $userID,
 +                                    'userID' => $comment->userID,
 +                                ]
 +                            );
 +
 +                            $recipientIDs = \array_diff($recipientIDs, [$userID]);
 +                        }
 +                    }
 +
 +                    if (!empty($recipientIDs)) {
 +                        UserNotificationHandler::getInstance()->fireEvent(
 +                            'commentResponse',
 +                            $objectType->objectType . '.response.notification',
 +                            $notificationObject,
 +                            $recipientIDs,
 +                            [
 +                                'commentID' => $comment->commentID,
 +                                'objectID' => $comment->objectID,
 +                                'userID' => $comment->userID,
 +                            ]
 +                        );
 +                    }
 +                } else {
 +                    /** @var ICommentUserNotificationObjectType $notificationObjectType */
 +                    $userID = $notificationObjectType->getOwnerID($comment->commentID);
 +
 +                    if ($comment->userID != WCF::getUser()->userID) {
 +                        UserNotificationHandler::getInstance()->fireEvent(
 +                            'commentResponse',
 +                            $objectType->objectType . '.response.notification',
 +                            $notificationObject,
 +                            [$comment->userID],
 +                            [
 +                                'commentID' => $comment->commentID,
 +                                'objectID' => $comment->objectID,
 +                                'objectUserID' => $userID,
 +                                'userID' => $comment->userID,
 +                            ]
 +                        );
 +                    }
 +
 +                    // notify the container owner
 +                    if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.notification')) {
 +                        if ($userID != $comment->userID && $userID != WCF::getUser()->userID) {
 +                            UserNotificationHandler::getInstance()->fireEvent(
 +                                'commentResponseOwner',
 +                                $objectType->objectType . '.response.notification',
 +                                $notificationObject,
 +                                [$userID],
 +                                [
 +                                    'commentID' => $comment->commentID,
 +                                    'objectID' => $comment->objectID,
 +                                    'objectUserID' => $userID,
 +                                    'userID' => $comment->userID,
 +                                ]
 +                            );
 +                        }
 +                    }
 +                }
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Validates the `enable` action.
 +     *
 +     * @throws  PermissionDeniedException
 +     */
 +    public function validateEnable()
 +    {
 +        $this->comment = $this->getSingleObject()->getDecoratedObject();
 +
 +        $objectType = $this->validateObjectType($this->comment->objectTypeID);
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Enables a comment.
 +     *
 +     * @return  int[]
 +     */
 +    public function enable()
 +    {
 +        if ($this->comment === null) {
 +            $this->comment = \reset($this->objects);
 +        }
 +
 +        if ($this->comment->isDisabled) {
 +            $action = new self([$this->comment], 'triggerPublication', [
 +                'commentProcessor' => $this->commentProcessor,
 +                'objectTypeID' => $this->comment->objectTypeID,
 +            ]);
 +            $action->executeAction();
 +        }
 +
 +        ModerationQueueActivationManager::getInstance()->removeModeratedContent(
 +            'com.woltlab.wcf.comment.comment',
 +            [$this->comment->commentID]
 +        );
 +
 +        return ['commentID' => $this->comment->commentID];
 +    }
 +
 +    /**
 +     * Validates the `enableResponse` action.
 +     *
 +     * @throws  PermissionDeniedException
 +     * @throws  UserInputException
 +     */
 +    public function validateEnableResponse()
 +    {
 +        $this->readInteger('responseID', false, 'data');
 +        $this->response = new CommentResponse($this->parameters['data']['responseID']);
 +        if (!$this->response->responseID) {
 +            throw new UserInputException('responseID');
 +        }
 +
 +        $this->comment = $this->response->getComment();
 +
 +        $objectType = $this->validateObjectType($this->comment->objectTypeID);
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canModerate($this->comment->objectTypeID, $this->comment->objectID)) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Enables a response.
 +     *
 +     * @return  int[]
 +     */
 +    public function enableResponse()
 +    {
 +        if ($this->comment === null) {
 +            $this->comment = \reset($this->objects);
 +        }
 +        if ($this->response === null) {
 +            $this->response = \reset($this->parameters['responses']);
 +        }
 +
 +        if ($this->response->isDisabled) {
 +            $action = new self([], 'triggerPublicationResponse', [
 +                'commentProcessor' => $this->commentProcessor,
 +                'objectTypeID' => $this->comment->objectTypeID,
 +                'responses' => [$this->response],
 +            ]);
 +            $action->executeAction();
 +        }
 +
 +        ModerationQueueActivationManager::getInstance()->removeModeratedContent(
 +            'com.woltlab.wcf.comment.response',
 +            [$this->response->responseID]
 +        );
 +
 +        return ['responseID' => $this->response->responseID];
 +    }
 +
 +    /**
 +     * Validates parameters to edit a comment or a response.
 +     *
 +     * @throws  PermissionDeniedException
 +     * @throws  UserInputException
 +     */
 +    public function validatePrepareEdit()
 +    {
 +        // validate response id
 +        try {
 +            $this->validateResponseID();
 +        } catch (UserInputException $e) {
 +            throw new UserInputException('objectIDs');
 +        }
 +
 +        // validate object type id
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canEditResponse($this->response)) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Prepares editing of a comment or a response.
 +     *
 +     * @return  array
 +     */
 +    public function prepareEdit()
 +    {
 +        return [
 +            'action' => 'prepare',
 +            'message' => $this->response->message,
 +            'responseID' => $this->response->responseID,
 +        ];
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateEdit()
 +    {
 +        $this->validatePrepareEdit();
 +
 +        $this->validateMessage();
 +    }
 +
 +    /**
 +     * Edits a comment or response.
 +     *
 +     * @return  array
 +     */
 +    public function edit()
 +    {
 +        $editor = new CommentResponseEditor($this->response);
 +        $editor->update([
 +            'message' => $this->parameters['data']['message'],
 +        ]);
 +        $response = new CommentResponse($this->response->responseID);
 +
 +        return [
 +            'action' => 'saved',
 +            'message' => $response->getFormattedMessage(),
 +            'responseID' => $this->response->responseID,
 +        ];
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateBeginEdit()
 +    {
 +        $this->comment = $this->getSingleObject();
 +
 +        // validate object type id
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canEditComment($this->comment->getDecoratedObject())) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        $this->setDisallowedBBCodes();
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function beginEdit()
 +    {
 +        WCF::getTPL()->assign([
 +            'comment' => $this->comment,
 +            'wysiwygSelector' => 'commentEditor' . $this->comment->commentID,
 +        ]);
 +
 +        return [
 +            'actionName' => 'beginEdit',
 +            'template' => WCF::getTPL()->fetch('commentEditor', 'wcf'),
 +        ];
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateSave()
 +    {
 +        $this->validateBeginEdit();
 +
 +        $this->validateMessage();
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function save()
 +    {
 +        /** @var HtmlInputProcessor $htmlInputProcessor */
 +        $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 +
 +        $action = new self([$this->comment], 'update', [
 +            'data' => [
 +                'message' => $htmlInputProcessor->getHtml(),
 +            ],
 +        ]);
 +        $action->executeAction();
 +
 +        return [
 +            'actionName' => 'save',
 +            'message' => (new Comment($this->comment->commentID))->getFormattedMessage(),
 +        ];
 +    }
 +
 +    /**
 +     * Validates parameters to remove a comment or response.
 +     *
 +     * @throws  PermissionDeniedException
 +     * @throws  UserInputException
 +     */
 +    public function validateRemove()
 +    {
 +        // validate comment id or response id
 +        try {
 +            $this->validateCommentID();
 +        } catch (UserInputException $e) {
 +            try {
 +                $this->validateResponseID();
 +            } catch (UserInputException $e) {
 +                throw new UserInputException('objectIDs');
 +            }
 +        }
 +
 +        // validate object type id
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if ($this->comment !== null) {
 +            if (!$this->commentProcessor->canDeleteComment($this->comment)) {
 +                throw new PermissionDeniedException();
 +            }
 +        } else {
 +            if (!$this->commentProcessor->canDeleteResponse($this->response)) {
 +                throw new PermissionDeniedException();
 +            }
 +        }
 +    }
 +
 +    /**
 +     * Removes a comment or response.
 +     *
 +     * @return  int[]
 +     */
 +    public function remove()
 +    {
 +        if ($this->comment !== null) {
 +            $objectAction = new self([$this->comment], 'delete');
 +            $objectAction->executeAction();
 +
 +            return ['commentID' => $this->comment->commentID];
 +        } else {
 +            $objectAction = new CommentResponseAction([$this->response], 'delete');
 +            $objectAction->executeAction();
 +
 +            return ['responseID' => $this->response->responseID];
 +        }
 +    }
 +
 +    /**
 +     * Validates the 'getGuestDialog' action.
 +     *
 +     * @throws  PermissionDeniedException
 +     */
 +    public function validateGetGuestDialog()
 +    {
 +        if (WCF::getUser()->userID) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        try {
 +            CommentHandler::enforceFloodControl();
 +        } catch (NamedUserException $e) {
 +            throw new UserInputException('message', $e->getMessage());
 +        }
 +
 +        $this->readInteger('objectID', false, 'data');
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canAdd($this->parameters['data']['objectID'])) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        // validate message already at this point to make sure that the
 +        // message is valid when submitting the dialog to avoid having to
 +        // go back to the message to fix it
 +        $this->validateMessage();
 +    }
 +
 +    /**
 +     * Returns the dialog for guests when they try to write a comment letting
 +     * them enter a username and solving a captcha.
 +     *
 +     * @return  array
 +     * @throws  SystemException
 +     */
 +    public function getGuestDialog()
 +    {
 +        $captchaObjectType = null;
 +
 +        if (CAPTCHA_TYPE) {
 +            $captchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName(CAPTCHA_TYPE);
 +            if ($captchaObjectType === null) {
 +                throw new SystemException("Unknown captcha object type with name '" . CAPTCHA_TYPE . "'");
 +            }
 +
 +            if (!$captchaObjectType->getProcessor()->isAvailable()) {
 +                $captchaObjectType = null;
 +            }
 +        }
 +
 +        return [
 +            'useCaptcha' => $captchaObjectType !== null,
 +            'template' => WCF::getTPL()->fetch('commentAddGuestDialog', 'wcf', [
 +                'ajaxCaptcha' => true,
 +                'captchaID' => 'commentAdd',
 +                'captchaObjectType' => $captchaObjectType,
 +                'supportsAsyncCaptcha' => true,
 +                'username' => WCF::getSession()->getVar('username'),
 +            ]),
 +        ];
 +    }
 +
 +    /**
 +     * Renders a comment.
 +     *
 +     * @param Comment $comment
 +     * @param CommentResponse $response
 +     * @return  string|string[]
 +     */
 +    protected function renderComment(Comment $comment, ?CommentResponse $response = null)
 +    {
 +        $comment = new StructuredComment($comment);
 +        $comment->setIsDeletable($this->commentProcessor->canDeleteComment($comment->getDecoratedObject()));
 +        $comment->setIsEditable($this->commentProcessor->canEditComment($comment->getDecoratedObject()));
 +
 +        if ($response !== null) {
 +            // check if response is not visible
 +            /** @var CommentResponse $visibleResponse */
 +            foreach ($comment as $visibleResponse) {
 +                if ($visibleResponse->responseID == $response->responseID) {
 +                    $response = null;
 +                    break;
 +                }
 +            }
 +        }
 +
 +        // load last response time
 +        if ($comment->getDecoratedObject()->responses) {
 +            $sql = "SELECT      time
 +                    FROM        wcf" . WCF_N . "_comment_response
 +                    WHERE       commentID = ?
 +                    ORDER BY    time";
 +            $statement = WCF::getDB()->prepareStatement($sql, 1);
 +            $statement->execute([$comment->commentID]);
 +            $lastResponseTime = $statement->fetchSingleColumn();
 +            if ($lastResponseTime && $lastResponseTime > 1) {
 +                WCF::getTPL()->assign('commentLastResponseTime', ($lastResponseTime - 1));
 +            }
 +        }
 +
 +        WCF::getTPL()->assign([
 +            'commentCanModerate' => $this->commentProcessor->canModerate(
 +                $comment->getDecoratedObject()->objectTypeID,
 +                $comment->getDecoratedObject()->objectID
 +            ),
 +            'commentList' => [$comment],
 +            'commentManager' => $this->commentProcessor,
 +        ]);
 +
 +        // load like data
 +        if (MODULE_LIKE) {
 +            $likeData = [];
 +            $commentObjectType = ReactionHandler::getInstance()->getObjectType('com.woltlab.wcf.comment');
 +            ReactionHandler::getInstance()->loadLikeObjects($commentObjectType, [$comment->commentID]);
 +            $likeData['comment'] = ReactionHandler::getInstance()->getLikeObjects($commentObjectType);
 +
 +            $responseIDs = [];
 +            foreach ($comment as $visibleResponse) {
 +                $responseIDs[] = $visibleResponse->responseID;
 +            }
 +
 +            if ($response !== null) {
 +                $responseIDs[] = $response->responseID;
 +            }
 +
 +            if (!empty($responseIDs)) {
 +                $responseObjectType = ReactionHandler::getInstance()->getObjectType('com.woltlab.wcf.comment.response');
 +                ReactionHandler::getInstance()->loadLikeObjects($responseObjectType, $responseIDs);
 +                $likeData['response'] = ReactionHandler::getInstance()->getLikeObjects($responseObjectType);
 +            }
 +
 +            WCF::getTPL()->assign('likeData', $likeData);
 +        }
 +
 +        $template = WCF::getTPL()->fetch('commentList');
 +        if ($response === null) {
 +            return $template;
 +        }
 +
 +        return [
 +            'template' => $template,
 +            'response' => $this->renderResponse($response),
 +        ];
 +    }
 +
 +    /**
 +     * Renders a response.
 +     *
 +     * @param CommentResponse $response
 +     * @return  string
 +     */
 +    protected function renderResponse(CommentResponse $response)
 +    {
 +        $response = new StructuredCommentResponse($response);
 +        $response->setIsDeletable($this->commentProcessor->canDeleteResponse($response->getDecoratedObject()));
 +        $response->setIsEditable($this->commentProcessor->canEditResponse($response->getDecoratedObject()));
 +
 +        // render response
 +        WCF::getTPL()->assign([
 +            'responseList' => [$response],
 +            'commentCanModerate' => $this->commentProcessor->canModerate(
 +                $response->getComment()->objectTypeID,
 +                $response->getComment()->objectID
 +            ),
 +            'commentManager' => $this->commentProcessor,
 +        ]);
 +
 +        return WCF::getTPL()->fetch('commentResponseList');
 +    }
 +
 +    /**
 +     * Validates message parameters.
 +     *
 +     * @throws      UserInputException
 +     */
 +    protected function validateMessage()
 +    {
 +        $this->readString('message', false, 'data');
 +        $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
 +
 +        if (empty($this->parameters['data']['message'])) {
 +            throw new UserInputException('message');
 +        }
 +
 +        CommentHandler::enforceCensorship($this->parameters['data']['message']);
 +
 +        $this->setDisallowedBBCodes();
 +        $htmlInputProcessor = $this->getHtmlInputProcessor(
 +            $this->parameters['data']['message'],
 +            ($this->comment !== null ? $this->comment->commentID : 0)
 +        );
 +
 +        // search for disallowed bbcodes
 +        $disallowedBBCodes = $htmlInputProcessor->validate();
 +        if (!empty($disallowedBBCodes)) {
 +            throw new UserInputException(
 +                'text',
 +                WCF::getLanguage()->getDynamicVariable(
 +                    'wcf.message.error.disallowedBBCodes',
 +                    ['disallowedBBCodes' => $disallowedBBCodes]
 +                )
 +            );
 +        }
 +
 +        if ($htmlInputProcessor->appearsToBeEmpty()) {
 +            throw new UserInputException('message');
 +        }
 +
 +        $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
 +    }
 +
 +    /**
 +     * Validates object type id parameter.
 +     *
 +     * @param int $objectTypeID
 +     * @return  ObjectType
 +     * @throws  UserInputException
 +     */
 +    protected function validateObjectType($objectTypeID = null)
 +    {
 +        if ($objectTypeID === null) {
 +            $this->readInteger('objectTypeID', false, 'data');
 +            $objectTypeID = $this->parameters['data']['objectTypeID'];
 +        }
 +
 +        $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +        if ($objectType === null) {
 +            throw new UserInputException('objectTypeID');
 +        }
 +
 +        return $objectType;
 +    }
 +
 +    /**
 +     * Validates comment id parameter.
 +     *
 +     * @throws  UserInputException
 +     */
 +    protected function validateCommentID()
 +    {
 +        $this->readInteger('commentID', false, 'data');
 +
 +        $this->comment = new Comment($this->parameters['data']['commentID']);
 +        if ($this->comment === null || !$this->comment->commentID) {
 +            throw new UserInputException('commentID');
 +        }
 +    }
 +
 +    /**
 +     * Validates response id parameter.
 +     *
 +     * @throws  UserInputException
 +     */
 +    protected function validateResponseID()
 +    {
 +        if (isset($this->parameters['data']['responseID'])) {
 +            $this->response = new CommentResponse($this->parameters['data']['responseID']);
 +        }
 +        if ($this->response === null || !$this->response->responseID) {
 +            throw new UserInputException('responseID');
 +        }
 +    }
 +
 +    /**
 +     * Validates the username parameter.
 +     */
 +    protected function validateUsername()
 +    {
 +        if (WCF::getUser()->userID) {
 +            return;
 +        }
 +
 +        try {
 +            $this->readString('username', false, 'data');
 +
 +            if (!UserRegistrationUtil::isValidUsername($this->parameters['data']['username'])) {
 +                throw new UserInputException('username', 'invalid');
 +            }
 +            if (!UserUtil::isAvailableUsername($this->parameters['data']['username'])) {
 +                throw new UserInputException('username', 'notUnique');
 +            }
 +        } catch (UserInputException $e) {
 +            $this->validationErrors['username'] = $e->getType();
 +        }
 +    }
 +
 +    /**
 +     * Validates the captcha challenge.
 +     *
 +     * @throws  SystemException
 +     */
 +    protected function validateCaptcha()
 +    {
 +        if (WCF::getUser()->userID) {
 +            return;
 +        }
 +
 +        if (CAPTCHA_TYPE) {
 +            $this->captchaObjectType = CaptchaHandler::getInstance()->getObjectTypeByName(CAPTCHA_TYPE);
 +            if ($this->captchaObjectType === null) {
 +                throw new SystemException("Unknown captcha object type with name '" . CAPTCHA_TYPE . "'");
 +            }
 +
 +            if (!$this->captchaObjectType->getProcessor()->isAvailable()) {
 +                $this->captchaObjectType = null;
 +            }
 +        }
 +
 +        if ($this->captchaObjectType === null) {
 +            return;
 +        }
 +
 +        try {
 +            $this->captchaObjectType->getProcessor()->readFormParameters();
 +            $this->captchaObjectType->getProcessor()->validate();
 +        } catch (UserInputException $e) {
 +            $this->validationErrors = \array_merge($this->validationErrors, [$e->getField() => $e->getType()]);
 +        }
 +    }
 +
 +    /**
 +     * Sets the list of disallowed bbcodes for comments.
 +     */
 +    protected function setDisallowedBBCodes()
 +    {
 +        BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
 +            ',',
 +            WCF::getSession()->getPermission('user.comment.disallowedBBCodes')
 +        ));
 +    }
 +
 +    /**
 +     * Returns the current html input processor or a new one if `$message` is not null.
 +     *
 +     * @param string|null $message source message
 +     * @param int $objectID object id
 +     * @return      HtmlInputProcessor
 +     */
 +    public function getHtmlInputProcessor($message = null, $objectID = 0)
 +    {
 +        if ($message === null) {
 +            return $this->htmlInputProcessor;
 +        }
 +
 +        $this->htmlInputProcessor = new HtmlInputProcessor();
 +        $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
 +
 +        return $this->htmlInputProcessor;
 +    }
 +
 +    /**
 +     * Returns the comment object.
 +     *
 +     * @return  Comment
 +     */
 +    public function getComment()
 +    {
 +        return $this->comment;
 +    }
 +
 +    /**
 +     * Returns the comment response object.
 +     *
 +     * @return  CommentResponse
 +     */
 +    public function getResponse()
 +    {
 +        return $this->response;
 +    }
 +
 +    /**
 +     * Returns the comment manager.
 +     *
 +     * @return  ICommentManager
 +     */
 +    public function getCommentManager()
 +    {
 +        return $this->commentProcessor;
 +    }
  }
index 6fcd93de758e3f673bfc1ead93c8642cddcfa491,68efcd0a1cd61632980640ead95e7329f87d3844..83d113e72a6181f115ddaa9b681657753f72c6c1
@@@ -14,7 -12,8 +14,8 @@@ use wcf\system\comment\manager\IComment
  use wcf\system\exception\PermissionDeniedException;
  use wcf\system\exception\UserInputException;
  use wcf\system\html\input\HtmlInputProcessor;
 -use wcf\system\reaction\ReactionHandler;
+ use wcf\system\moderation\queue\ModerationQueueManager;
 +use wcf\system\reaction\ReactionHandler;
  use wcf\system\user\activity\event\UserActivityEventHandler;
  use wcf\system\user\notification\UserNotificationHandler;
  use wcf\system\WCF;
@@@ -22,393 -21,355 +23,398 @@@ use wcf\util\MessageUtil
  
  /**
   * Executes comment response-related actions.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2019 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @package   WoltLabSuite\Core\Data\Comment\Response
 - * 
 - * @method    CommentResponse                 create()
 - * @method    CommentResponseEditor[]         getObjects()
 - * @method    CommentResponseEditor           getSingleObject()
 + *
 + * @author  Alexander Ebert
 + * @copyright   2001-2019 WoltLab GmbH
 + * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @package WoltLabSuite\Core\Data\Comment\Response
 + *
 + * @method  CommentResponse         create()
 + * @method  CommentResponseEditor[]     getObjects()
 + * @method  CommentResponseEditor       getSingleObject()
   */
 -class CommentResponseAction extends AbstractDatabaseObjectAction {
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $allowGuestAccess = ['loadResponses'];
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $className = CommentResponseEditor::class;
 -      
 -      /**
 -       * comment object
 -       * @var Comment
 -       */
 -      public $comment;
 -      
 -      /**
 -       * comment manager object
 -       * @var ICommentManager
 -       */
 -      public $commentManager;
 -      
 -      /**
 -       * comment processor
 -       * @var ICommentManager
 -       */
 -      protected $commentProcessor;
 -      
 -      /**
 -       * @var HtmlInputProcessor
 -       */
 -      protected $htmlInputProcessor;
 -      
 -      /**
 -       * response object
 -       * @var CommentResponse
 -       */
 -      protected $response;
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function delete() {
 -              if (empty($this->objects)) {
 -                      $this->readObjects();
 -              }
 -              
 -              if (empty($this->objects)) {
 -                      return 0;
 -              }
 -              
 -              $ignoreCounters = !empty($this->parameters['ignoreCounters']);
 -              
 -              // read object type ids for comments
 -              $commentIDs = [];
 -              foreach ($this->getObjects() as $response) {
 -                      $commentIDs[] = $response->commentID;
 -              }
 -              
 -              $commentList = new CommentList();
 -              $commentList->setObjectIDs($commentIDs);
 -              $commentList->readObjects();
 -              $comments = $commentList->getObjects();
 -              
 -              // update counters
 -              /** @var ICommentManager[] $processors */
 -              $processors = $responseIDs = $updateComments = [];
 -              foreach ($this->getObjects() as $response) {
 -                      $objectTypeID = $comments[$response->commentID]->objectTypeID;
 -                      
 -                      if (!isset($processors[$objectTypeID])) {
 -                              $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -                              $processors[$objectTypeID] = $objectType->getProcessor();
 -                              $responseIDs[$objectTypeID] = [];
 -                      }
 -                      $responseIDs[$objectTypeID][] = $response->responseID;
 -                      
 -                      if (!$ignoreCounters && !$response->isDisabled) {
 -                              $processors[$objectTypeID]->updateCounter($comments[$response->commentID]->objectID, -1);
 -                              
 -                              if (!isset($updateComments[$response->commentID])) {
 -                                      $updateComments[$response->commentID] = 0;
 -                              }
 -                              
 -                              $updateComments[$response->commentID]++;
 -                      }
 -              }
 -              
 -              // remove responses
 -              $count = parent::delete();
 -              
 -              // update comment responses and cached response ids
 -              if (!$ignoreCounters) {
 -                      foreach ($comments as $comment) {
 -                              $commentEditor = new CommentEditor($comment);
 -                              $commentEditor->updateResponseIDs();
 -                              if (isset($updateComments[$comment->commentID])) {
 -                                      $commentEditor->updateCounters([
 -                                              'responses' => -1 * $updateComments[$comment->commentID]
 -                                      ]);
 -                              }
 -                      }
 -              }
 -              
 -              $deletedResponseIDs = [];
 -              $notificationObjectTypes = [];
 -              foreach ($responseIDs as $objectTypeID => $objectIDs) {
 -                      // remove activity events
 -                      $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -                      if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.recentActivityEvent')) {
 -                              UserActivityEventHandler::getInstance()->removeEvents($objectType->objectType.'.response.recentActivityEvent', $objectIDs);
 -                      }
 -                      
 -                      // delete notifications
 -                      if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.notification')) {
 -                              UserNotificationHandler::getInstance()->removeNotifications($objectType->objectType.'.response.notification', $objectIDs);
 -                      }
 -                      
 -                      $deletedResponseIDs = array_merge($deletedResponseIDs, $objectIDs);
 -                      
 -                      if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType.'.response.like.notification')) {
 -                              $notificationObjectTypes[] = $objectType->objectType.'.response.like.notification';
 -                      }
 -              }
 -              
 -              if (!empty($deletedResponseIDs)) {
 -                      // remove likes
 -                      ReactionHandler::getInstance()->removeReactions('com.woltlab.wcf.comment.response', $deletedResponseIDs, $notificationObjectTypes);
 -                      
 -                      ModerationQueueManager::getInstance()->removeQueues(
 -                              'com.woltlab.wcf.comment.response',
 -                              $deletedResponseIDs
 -                      );
 -              }
 -              
 -              return $count;
 -      }
 -      
 -      /**
 -       * Validates parameters to load responses for a given comment id.
 -       */
 -      public function validateLoadResponses() {
 -              $this->readInteger('commentID', false, 'data');
 -              $this->readInteger('lastResponseTime', false, 'data');
 -              $this->readBoolean('loadAllResponses', true, 'data');
 -              
 -              $this->comment = new Comment($this->parameters['data']['commentID']);
 -              if (!$this->comment->commentID) {
 -                      throw new UserInputException('commentID');
 -              }
 -              
 -              $this->commentManager = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID)->getProcessor();
 -              if (!$this->commentManager->isAccessible($this->comment->objectID)) {
 -                      throw new PermissionDeniedException();
 -              }
 -      }
 -      
 -      /**
 -       * Returns parsed responses for given comment id.
 -       * 
 -       * @return      array
 -       */
 -      public function loadResponses() {
 -              $commentCanModerate = $this->commentManager->canModerate($this->comment->objectTypeID, $this->comment->objectID);
 -              
 -              // get response list
 -              $responseList = new StructuredCommentResponseList($this->commentManager, $this->comment);
 -              $responseList->getConditionBuilder()->add("comment_response.time > ?", [$this->parameters['data']['lastResponseTime']]);
 -              if (!$commentCanModerate) $responseList->getConditionBuilder()->add("comment_response.isDisabled = ?", [0]);
 -              if (!$this->parameters['data']['loadAllResponses']) $responseList->sqlLimit = 50;
 -              $responseList->readObjects();
 -              
 -              $lastResponseTime = 0;
 -              foreach ($responseList as $response) {
 -                      if (!$lastResponseTime) {
 -                              $lastResponseTime = $response->time;
 -                      }
 -                      
 -                      $lastResponseTime = max($lastResponseTime, $response->time);
 -              }
 -              
 -              // mark notifications for loaded responses as read
 -              CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
 -                      CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID)->objectType,
 -                      $responseList->getObjects()
 -              );
 -              
 -              WCF::getTPL()->assign([
 -                      'commentCanModerate' => $commentCanModerate,
 -                      'likeData' => MODULE_LIKE ? $responseList->getLikeData() : [],
 -                      'responseList' => $responseList,
 -                      'commentManager' => $this->commentManager
 -              ]);
 -              
 -              return [
 -                      'commentID' => $this->comment->commentID,
 -                      'lastResponseTime' => $lastResponseTime,
 -                      'template' => WCF::getTPL()->fetch('commentResponseList')
 -              ];
 -      }
 -      
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateBeginEdit() {
 -              $this->response = $this->getSingleObject();
 -              
 -              // validate object type id
 -              $objectType = $this->validateObjectType();
 -              
 -              // validate object id and permissions
 -              $this->commentProcessor = $objectType->getProcessor();
 -              if (!$this->commentProcessor->canEditResponse($this->response->getDecoratedObject())) {
 -                      throw new PermissionDeniedException();
 -              }
 -              
 -              $this->setDisallowedBBCodes();
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function beginEdit() {
 -              WCF::getTPL()->assign([
 -                      'response' => $this->response,
 -                      'wysiwygSelector' => 'commentResponseEditor'.$this->response->responseID
 -              ]);
 -              
 -              return [
 -                      'actionName' => 'beginEdit',
 -                      'template' => WCF::getTPL()->fetch('commentResponseEditor', 'wcf')
 -              ];
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function validateSave() {
 -              $this->validateBeginEdit();
 -              
 -              $this->validateMessage();
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function save() {
 -              /** @var HtmlInputProcessor $htmlInputProcessor */
 -              $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 -              
 -              $action = new CommentResponseAction([$this->response], 'update', [
 -                      'data' => [
 -                              'message' => $htmlInputProcessor->getHtml()
 -                      ]
 -              ]);
 -              $action->executeAction();
 -              
 -              return [
 -                      'actionName' => 'save',
 -                      'message' => (new CommentResponse($this->response->responseID))->getFormattedMessage()
 -              ];
 -      }
 -      
 -      /**
 -       * Validates message parameter.
 -       *
 -       * @throws      UserInputException
 -       */
 -      protected function validateMessage() {
 -              $this->readString('message', false, 'data');
 -              $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
 -              
 -              if (empty($this->parameters['data']['message'])) {
 -                      throw new UserInputException('message');
 -              }
 -              
 -              CommentHandler::enforceCensorship($this->parameters['data']['message']);
 -              
 -              $this->setDisallowedBBCodes();
 -              $htmlInputProcessor = $this->getHtmlInputProcessor($this->parameters['data']['message'], ($this->comment !== null ? $this->comment->commentID : 0));
 -              
 -              // search for disallowed bbcodes
 -              $disallowedBBCodes = $htmlInputProcessor->validate();
 -              if (!empty($disallowedBBCodes)) {
 -                      throw new UserInputException('text', WCF::getLanguage()->getDynamicVariable('wcf.message.error.disallowedBBCodes', ['disallowedBBCodes' => $disallowedBBCodes]));
 -              }
 -              
 -              if ($htmlInputProcessor->appearsToBeEmpty()) {
 -                      throw new UserInputException('message');
 -              }
 -              
 -              $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
 -      }
 -      
 -      /**
 -       * Validates object type id parameter.
 -       *
 -       * @param       integer         $objectTypeID
 -       * @return      ObjectType
 -       * @throws      UserInputException
 -       */
 -      protected function validateObjectType($objectTypeID = null) {
 -              if ($objectTypeID === null) {
 -                      $this->readInteger('objectTypeID', false, 'data');
 -                      $objectTypeID = $this->parameters['data']['objectTypeID'];
 -              }
 -              
 -              $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 -              if ($objectType === null) {
 -                      throw new UserInputException('objectTypeID');
 -              }
 -              
 -              return $objectType;
 -      }
 -      
 -      /**
 -       * Sets the list of disallowed bbcodes for comments.
 -       */
 -      protected function setDisallowedBBCodes() {
 -              BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.comment.disallowedBBCodes')));
 -      }
 -      
 -      /**
 -       * Returns the current html input processor or a new one if `$message` is not null.
 -       *
 -       * @param       string|null     $message        source message
 -       * @param       integer         $objectID       object id
 -       * @return      HtmlInputProcessor
 -       */
 -      public function getHtmlInputProcessor($message = null, $objectID = 0) {
 -              if ($message === null) {
 -                      return $this->htmlInputProcessor;
 -              }
 -              
 -              $this->htmlInputProcessor = new HtmlInputProcessor();
 -              $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
 -              
 -              return $this->htmlInputProcessor;
 -      }
 +class CommentResponseAction extends AbstractDatabaseObjectAction
 +{
 +    /**
 +     * @inheritDoc
 +     */
 +    protected $allowGuestAccess = ['loadResponses'];
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    protected $className = CommentResponseEditor::class;
 +
 +    /**
 +     * comment object
 +     * @var Comment
 +     */
 +    public $comment;
 +
 +    /**
 +     * comment manager object
 +     * @var ICommentManager
 +     */
 +    public $commentManager;
 +
 +    /**
 +     * comment processor
 +     * @var ICommentManager
 +     */
 +    protected $commentProcessor;
 +
 +    /**
 +     * @var HtmlInputProcessor
 +     */
 +    protected $htmlInputProcessor;
 +
 +    /**
 +     * response object
 +     * @var CommentResponse
 +     */
 +    protected $response;
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function delete()
 +    {
 +        if (empty($this->objects)) {
 +            $this->readObjects();
 +        }
 +
 +        if (empty($this->objects)) {
 +            return 0;
 +        }
 +
 +        $ignoreCounters = !empty($this->parameters['ignoreCounters']);
 +
 +        // read object type ids for comments
 +        $commentIDs = [];
 +        foreach ($this->getObjects() as $response) {
 +            $commentIDs[] = $response->commentID;
 +        }
 +
 +        $commentList = new CommentList();
 +        $commentList->setObjectIDs($commentIDs);
 +        $commentList->readObjects();
 +        $comments = $commentList->getObjects();
 +
 +        // update counters
 +        /** @var ICommentManager[] $processors */
 +        $processors = $responseIDs = $updateComments = [];
 +        foreach ($this->getObjects() as $response) {
 +            $objectTypeID = $comments[$response->commentID]->objectTypeID;
 +
 +            if (!isset($processors[$objectTypeID])) {
 +                $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +                $processors[$objectTypeID] = $objectType->getProcessor();
 +                $responseIDs[$objectTypeID] = [];
 +            }
 +            $responseIDs[$objectTypeID][] = $response->responseID;
 +
 +            if (!$ignoreCounters && !$response->isDisabled) {
 +                $processors[$objectTypeID]->updateCounter($comments[$response->commentID]->objectID, -1);
 +
 +                if (!isset($updateComments[$response->commentID])) {
 +                    $updateComments[$response->commentID] = 0;
 +                }
 +
 +                $updateComments[$response->commentID]++;
 +            }
 +        }
 +
 +        // remove responses
 +        $count = parent::delete();
 +
 +        // update comment responses and cached response ids
 +        if (!$ignoreCounters) {
 +            foreach ($comments as $comment) {
 +                $commentEditor = new CommentEditor($comment);
 +                $commentEditor->updateResponseIDs();
 +                if (isset($updateComments[$comment->commentID])) {
 +                    $commentEditor->updateCounters([
 +                        'responses' => -1 * $updateComments[$comment->commentID],
 +                    ]);
 +                }
 +            }
 +        }
 +
-         $likeObjectIDs = [];
++        $deletedResponseIDs = [];
 +        $notificationObjectTypes = [];
 +        foreach ($responseIDs as $objectTypeID => $objectIDs) {
 +            // remove activity events
 +            $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +            if (UserActivityEventHandler::getInstance()->getObjectTypeID($objectType->objectType . '.response.recentActivityEvent')) {
 +                UserActivityEventHandler::getInstance()->removeEvents(
 +                    $objectType->objectType . '.response.recentActivityEvent',
 +                    $objectIDs
 +                );
 +            }
 +
 +            // delete notifications
 +            if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.response.notification')) {
 +                UserNotificationHandler::getInstance()->removeNotifications(
 +                    $objectType->objectType . '.response.notification',
 +                    $objectIDs
 +                );
 +            }
 +
-             $likeObjectIDs = \array_merge($likeObjectIDs, $objectIDs);
++            $deletedResponseIDs = \array_merge($deletedResponseIDs, $objectIDs);
 +
 +            if (UserNotificationHandler::getInstance()->getObjectTypeID($objectType->objectType . '.response.like.notification')) {
 +                $notificationObjectTypes[] = $objectType->objectType . '.response.like.notification';
 +            }
 +        }
 +
-         // remove likes
-         if (!empty($likeObjectIDs)) {
++        if (!empty($deletedResponseIDs)) {
++            // remove likes
 +            ReactionHandler::getInstance()->removeReactions(
 +                'com.woltlab.wcf.comment.response',
-                 $likeObjectIDs,
++                $deletedResponseIDs,
 +                $notificationObjectTypes
 +            );
++
++            ModerationQueueManager::getInstance()->removeQueues(
++                'com.woltlab.wcf.comment.response',
++                $deletedResponseIDs
++            );
 +        }
 +
 +        return $count;
 +    }
 +
 +    /**
 +     * Validates parameters to load responses for a given comment id.
 +     */
 +    public function validateLoadResponses()
 +    {
 +        $this->readInteger('commentID', false, 'data');
 +        $this->readInteger('lastResponseTime', false, 'data');
 +        $this->readBoolean('loadAllResponses', true, 'data');
 +
 +        $this->comment = new Comment($this->parameters['data']['commentID']);
 +        if (!$this->comment->commentID) {
 +            throw new UserInputException('commentID');
 +        }
 +
 +        $this->commentManager = ObjectTypeCache::getInstance()->getObjectType($this->comment->objectTypeID)->getProcessor();
 +        if (!$this->commentManager->isAccessible($this->comment->objectID)) {
 +            throw new PermissionDeniedException();
 +        }
 +    }
 +
 +    /**
 +     * Returns parsed responses for given comment id.
 +     *
 +     * @return  array
 +     */
 +    public function loadResponses()
 +    {
 +        $commentCanModerate = $this->commentManager->canModerate(
 +            $this->comment->objectTypeID,
 +            $this->comment->objectID
 +        );
 +
 +        // get response list
 +        $responseList = new StructuredCommentResponseList($this->commentManager, $this->comment);
 +        $responseList->getConditionBuilder()->add(
 +            "comment_response.time > ?",
 +            [$this->parameters['data']['lastResponseTime']]
 +        );
 +        if (!$commentCanModerate) {
 +            $responseList->getConditionBuilder()->add("comment_response.isDisabled = ?", [0]);
 +        }
 +        if (!$this->parameters['data']['loadAllResponses']) {
 +            $responseList->sqlLimit = 50;
 +        }
 +        $responseList->readObjects();
 +
 +        $lastResponseTime = 0;
 +        foreach ($responseList as $response) {
 +            if (!$lastResponseTime) {
 +                $lastResponseTime = $response->time;
 +            }
 +
 +            $lastResponseTime = \max($lastResponseTime, $response->time);
 +        }
 +
 +        // mark notifications for loaded responses as read
 +        CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
 +            CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID)->objectType,
 +            $responseList->getObjects()
 +        );
 +
 +        WCF::getTPL()->assign([
 +            'commentCanModerate' => $commentCanModerate,
 +            'likeData' => MODULE_LIKE ? $responseList->getLikeData() : [],
 +            'responseList' => $responseList,
 +            'commentManager' => $this->commentManager,
 +        ]);
 +
 +        return [
 +            'commentID' => $this->comment->commentID,
 +            'lastResponseTime' => $lastResponseTime,
 +            'template' => WCF::getTPL()->fetch('commentResponseList'),
 +        ];
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateBeginEdit()
 +    {
 +        $this->response = $this->getSingleObject();
 +
 +        // validate object type id
 +        $objectType = $this->validateObjectType();
 +
 +        // validate object id and permissions
 +        $this->commentProcessor = $objectType->getProcessor();
 +        if (!$this->commentProcessor->canEditResponse($this->response->getDecoratedObject())) {
 +            throw new PermissionDeniedException();
 +        }
 +
 +        $this->setDisallowedBBCodes();
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function beginEdit()
 +    {
 +        WCF::getTPL()->assign([
 +            'response' => $this->response,
 +            'wysiwygSelector' => 'commentResponseEditor' . $this->response->responseID,
 +        ]);
 +
 +        return [
 +            'actionName' => 'beginEdit',
 +            'template' => WCF::getTPL()->fetch('commentResponseEditor', 'wcf'),
 +        ];
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function validateSave()
 +    {
 +        $this->validateBeginEdit();
 +
 +        $this->validateMessage();
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function save()
 +    {
 +        /** @var HtmlInputProcessor $htmlInputProcessor */
 +        $htmlInputProcessor = $this->parameters['htmlInputProcessor'];
 +
 +        $action = new self([$this->response], 'update', [
 +            'data' => [
 +                'message' => $htmlInputProcessor->getHtml(),
 +            ],
 +        ]);
 +        $action->executeAction();
 +
 +        return [
 +            'actionName' => 'save',
 +            'message' => (new CommentResponse($this->response->responseID))->getFormattedMessage(),
 +        ];
 +    }
 +
 +    /**
 +     * Validates message parameter.
 +     *
 +     * @throws      UserInputException
 +     */
 +    protected function validateMessage()
 +    {
 +        $this->readString('message', false, 'data');
 +        $this->parameters['data']['message'] = MessageUtil::stripCrap($this->parameters['data']['message']);
 +
 +        if (empty($this->parameters['data']['message'])) {
 +            throw new UserInputException('message');
 +        }
 +
 +        CommentHandler::enforceCensorship($this->parameters['data']['message']);
 +
 +        $this->setDisallowedBBCodes();
 +        $htmlInputProcessor = $this->getHtmlInputProcessor(
 +            $this->parameters['data']['message'],
 +            ($this->comment !== null ? $this->comment->commentID : 0)
 +        );
 +
 +        // search for disallowed bbcodes
 +        $disallowedBBCodes = $htmlInputProcessor->validate();
 +        if (!empty($disallowedBBCodes)) {
 +            throw new UserInputException(
 +                'text',
 +                WCF::getLanguage()->getDynamicVariable(
 +                    'wcf.message.error.disallowedBBCodes',
 +                    ['disallowedBBCodes' => $disallowedBBCodes]
 +                )
 +            );
 +        }
 +
 +        if ($htmlInputProcessor->appearsToBeEmpty()) {
 +            throw new UserInputException('message');
 +        }
 +
 +        $this->parameters['htmlInputProcessor'] = $htmlInputProcessor;
 +    }
 +
 +    /**
 +     * Validates object type id parameter.
 +     *
 +     * @param int $objectTypeID
 +     * @return  ObjectType
 +     * @throws  UserInputException
 +     */
 +    protected function validateObjectType($objectTypeID = null)
 +    {
 +        if ($objectTypeID === null) {
 +            $this->readInteger('objectTypeID', false, 'data');
 +            $objectTypeID = $this->parameters['data']['objectTypeID'];
 +        }
 +
 +        $objectType = ObjectTypeCache::getInstance()->getObjectType($objectTypeID);
 +        if ($objectType === null) {
 +            throw new UserInputException('objectTypeID');
 +        }
 +
 +        return $objectType;
 +    }
 +
 +    /**
 +     * Sets the list of disallowed bbcodes for comments.
 +     */
 +    protected function setDisallowedBBCodes()
 +    {
 +        BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
 +            ',',
 +            WCF::getSession()->getPermission('user.comment.disallowedBBCodes')
 +        ));
 +    }
 +
 +    /**
 +     * Returns the current html input processor or a new one if `$message` is not null.
 +     *
 +     * @param string|null $message source message
 +     * @param int $objectID object id
 +     * @return      HtmlInputProcessor
 +     */
 +    public function getHtmlInputProcessor($message = null, $objectID = 0)
 +    {
 +        if ($message === null) {
 +            return $this->htmlInputProcessor;
 +        }
 +
 +        $this->htmlInputProcessor = new HtmlInputProcessor();
 +        $this->htmlInputProcessor->process($message, 'com.woltlab.wcf.comment', $objectID);
 +
 +        return $this->htmlInputProcessor;
 +    }
  }
index 93262c7b2811dd2b9dd218d4b93cf1590c10ece6,ef1a670c4a9202590508f9c87401f4ea5e412029..373ba3eee32214bd04dfa95c38918cd9c3d1bfca
@@@ -9,35 -7,36 +9,40 @@@ use wcf\system\moderation\queue\Abstrac
  
  /**
   * An implementation of IModerationQueueReportHandler for comment responses.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2019 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @package   WoltLabSuite\Core\System\Moderation\Queue
 + *
 + * @author  Alexander Ebert
 + * @copyright   2001-2019 WoltLab GmbH
 + * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @package WoltLabSuite\Core\System\Moderation\Queue
   */
 -class CommentResponseModerationQueueActivationHandler extends AbstractCommentResponseModerationQueueHandler implements IModerationQueueActivationHandler {
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $definitionName = 'com.woltlab.wcf.moderation.activation';
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function enableContent(ModerationQueue $queue) {
 -              if ($this->isValid($queue->objectID) && $this->getResponse($queue->objectID)->isDisabled) {
 -                      $response = $this->getResponse($queue->objectID);
 -                      
 -                      $commentAction = new CommentAction([$this->getComment($response->commentID)], 'enableResponse', [
 -                              'responses' => [$response]
 -                      ]);
 -                      $commentAction->executeAction();
 -              }
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getDisabledContent(ViewableModerationQueue $queue) {
 -              return $this->getRelatedContent($queue);
 -      }
 +class CommentResponseModerationQueueActivationHandler extends AbstractCommentResponseModerationQueueHandler implements
 +    IModerationQueueActivationHandler
 +{
++    /**
++     * @inheritDoc
++     */
++    protected $definitionName = 'com.woltlab.wcf.moderation.activation';
++
 +    /**
 +     * @inheritDoc
 +     */
 +    public function enableContent(ModerationQueue $queue)
 +    {
 +        if ($this->isValid($queue->objectID) && $this->getResponse($queue->objectID)->isDisabled) {
 +            $response = $this->getResponse($queue->objectID);
 +
 +            $commentAction = new CommentAction([$this->getComment($response->commentID)], 'enableResponse', [
 +                'responses' => [$response],
 +            ]);
 +            $commentAction->executeAction();
 +        }
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getDisabledContent(ViewableModerationQueue $queue)
 +    {
 +        return $this->getRelatedContent($queue);
 +    }
  }
index c304ad8df847dcd172deb7b21a00d1ddadc9e05a,b43bec10aa3b592aea3c05ea6a335e26232f1fbd..b1c839d8192bcd5f36e818a3b17c2ff61cead453
@@@ -7,46 -5,46 +7,51 @@@ use wcf\system\moderation\queue\Abstrac
  
  /**
   * An implementation of IModerationQueueReportHandler for comment responses.
 - * 
 - * @author    Alexander Ebert
 - * @copyright 2001-2019 WoltLab GmbH
 - * @license   GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 - * @package   WoltLabSuite\Core\System\Moderation\Queue
 + *
 + * @author  Alexander Ebert
 + * @copyright   2001-2019 WoltLab GmbH
 + * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
 + * @package WoltLabSuite\Core\System\Moderation\Queue
   */
 -class CommentResponseModerationQueueReportHandler extends AbstractCommentResponseModerationQueueHandler implements IModerationQueueReportHandler {
 -      /**
 -       * @inheritDoc
 -       */
 -      protected $definitionName = 'com.woltlab.wcf.moderation.report';
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function canReport($objectID) {
 -              if (!$this->isValid($objectID)) {
 -                      return false;
 -              }
 -              
 -              $response = $this->getResponse($objectID);
 -              $comment = $this->getComment($response->commentID);
 -              if (!$this->getCommentManager($comment)->isAccessible($comment->objectID)) {
 -                      return false;
 -              }
 -              
 -              return true;
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getReportedContent(ViewableModerationQueue $queue) {
 -              return $this->getRelatedContent($queue);
 -      }
 -      
 -      /**
 -       * @inheritDoc
 -       */
 -      public function getReportedObject($objectID) {
 -              return $this->getResponse($objectID);
 -      }
 +class CommentResponseModerationQueueReportHandler extends AbstractCommentResponseModerationQueueHandler implements
 +    IModerationQueueReportHandler
 +{
++    /**
++     * @inheritDoc
++     */
++    protected $definitionName = 'com.woltlab.wcf.moderation.report';
++
 +    /**
 +     * @inheritDoc
 +     */
 +    public function canReport($objectID)
 +    {
 +        if (!$this->isValid($objectID)) {
 +            return false;
 +        }
 +
 +        $response = $this->getResponse($objectID);
 +        $comment = $this->getComment($response->commentID);
 +        if (!$this->getCommentManager($comment)->isAccessible($comment->objectID)) {
 +            return false;
 +        }
 +
 +        return true;
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getReportedContent(ViewableModerationQueue $queue)
 +    {
 +        return $this->getRelatedContent($queue);
 +    }
 +
 +    /**
 +     * @inheritDoc
 +     */
 +    public function getReportedObject($objectID)
 +    {
 +        return $this->getResponse($objectID);
 +    }
  }