$event->register(new \wcf\system\endpoint\controller\core\files\PostGenerateThumbnails);
$event->register(new \wcf\system\endpoint\controller\core\files\PostUpload);
$event->register(new \wcf\system\endpoint\controller\core\files\upload\PostChunk);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\CreateComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\DeleteComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\EditComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\EnableComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\RenderComments);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\RenderComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\UpdateComment);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\CreateResponse);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\DeleteResponse);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\EditResponse);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\EnableResponse);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponse);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\RenderResponses);
+ $event->register(new \wcf\system\endpoint\controller\core\comments\responses\UpdateResponse);
$event->register(new \wcf\system\endpoint\controller\core\messages\GetMentionSuggestions);
$event->register(new \wcf\system\endpoint\controller\core\sessions\DeleteSession);
}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\event\message\MessageSpamChecking;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\flood\FloodControl;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * API endpoint for the creation of new comments.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments')]
+final class CreateComment implements IController
+{
+ use TCommentMessageValidator;
+
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ try {
+ CommentHandler::enforceFloodControl();
+ } catch (NamedUserException $e) {
+ throw new UserInputException('message', $e->getMessage());
+ }
+
+ $parameters = Helper::mapApiParameters($request, CreateCommentParameters::class);
+ $objectType = CommentHandler::getInstance()->getObjectType($parameters->objectTypeID);
+ if ($objectType === null) {
+ throw new UserInputException('objectTypeID');
+ }
+
+ if (!$objectType->getProcessor()->canAdd($parameters->objectID)) {
+ throw new PermissionDeniedException();
+ }
+
+ $username = '';
+ if (!WCF::getUser()->userID) {
+ $username = UserUtil::verifyGuestToken($parameters->guestToken);
+ if ($username === null) {
+ throw new UserInputException('guestToken');
+ }
+ }
+
+ $isDisabled = !$objectType->getProcessor()->canAddWithoutApproval($parameters->objectID);
+
+ $htmlInputProcessor = $this->validateMessage($parameters->message);
+
+ $event = new MessageSpamChecking(
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ $isDisabled = true;
+ }
+
+ $comment = (new \wcf\system\comment\command\CreateComment(
+ $objectType,
+ $parameters->objectID,
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ $username,
+ $isDisabled,
+ ))();
+
+ FloodControl::getInstance()->registerContent('com.woltlab.wcf.comment');
+
+ return new JsonResponse([
+ 'commentID' => $comment->commentID,
+ ]);
+ }
+}
+
+/** @internal */
+final class CreateCommentParameters
+{
+ public function __construct(
+ /** @var positive-int **/
+ public readonly int $objectID,
+
+ /** @var positive-int **/
+ public readonly int $objectTypeID,
+
+ /** @var non-empty-string */
+ public readonly string $message,
+
+ public readonly string $guestToken,
+ ) {
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\http\Helper;
+use wcf\system\endpoint\DeleteRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * API endpoint for the deletion of comments.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[DeleteRequest('/core/comments/{id:\d+}')]
+final class DeleteComment implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $comment = Helper::fetchObjectFromRequestParameter($variables['id'], Comment::class);
+
+ $this->assertCommentIsDeletable($comment);
+
+ (new \wcf\system\comment\command\DeleteComments([$comment]))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertCommentIsDeletable(Comment $comment): void
+ {
+ $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
+ if (!$objectType->getProcessor()->canDeleteComment($comment)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\http\Helper;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\html\upcast\HtmlUpcastProcessor;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for starting the editing of a comment.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/{id:\d+}/edit')]
+final class EditComment implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $comment = Helper::fetchObjectFromRequestParameter($variables['id'], Comment::class);
+
+ $this->assertCommentIsEditable($comment);
+
+ BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+ ',',
+ WCF::getSession()->getPermission('user.comment.disallowedBBCodes')
+ ));
+
+ $upcastProcessor = new HtmlUpcastProcessor();
+ $upcastProcessor->process($comment->message, 'com.woltlab.wcf.comment');
+
+ return new JsonResponse([
+ 'template' => WCF::getTPL()->fetch('commentEditor', 'wcf', [
+ 'comment' => $comment,
+ 'text' => $upcastProcessor->getHtml(),
+ 'wysiwygSelector' => 'commentEditor' . $comment->commentID,
+ ]),
+ ]);
+ }
+
+ private function assertCommentIsEditable(Comment $comment): void
+ {
+ $processor = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+ \assert($processor instanceof ICommentManager);
+ if (!$processor->canEditComment($comment)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * API endpoint for enabling comments.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments/{id:\d+}/enable')]
+final class EnableComment implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $comment = Helper::fetchObjectFromRequestParameter($variables['id'], Comment::class);
+
+ $this->assertCommentCanBeEnabled($comment);
+
+ (new \wcf\system\comment\command\PublishComment($comment))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertCommentCanBeEnabled(Comment $comment): void
+ {
+ if (!$comment->isDisabled) {
+ throw new IllegalLinkException();
+ }
+
+ $processor = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+ \assert($processor instanceof ICommentManager);
+ if (!$processor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\StructuredCommentResponse;
+use wcf\data\comment\StructuredComment;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\reaction\ReactionHandler;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for the rendering of a single comment.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/{id:\d+}/render')]
+final class RenderComment implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $comment = Helper::fetchObjectFromRequestParameter($variables['id'], Comment::class);
+
+ $parameters = Helper::mapQueryParameters(
+ $request->getQueryParams(),
+ <<<'EOT'
+ array {
+ responseID: null|positive-int,
+ messageOnly: null|bool,
+ objectTypeID: null|positive-int,
+ }
+ EOT,
+ );
+
+ $this->assertCommentIsAccessible($comment, $parameters['objectTypeID']);
+ $response = null;
+ if ($parameters['responseID']) {
+ $response = Helper::fetchObjectFromRequestParameter($parameters['responseID'], CommentResponse::class);
+ $this->assertResponseIsAccessible($comment, $response);
+ }
+
+ $this->markNotificationsAsRead($comment, $response);
+
+ return new JsonResponse(
+ $this->renderComment($comment, $response, $parameters['messageOnly'] ?? false),
+ );
+ }
+
+ private function assertCommentIsAccessible(Comment $comment, ?int $objectTypeID = null): void
+ {
+ $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
+ if ($objectTypeID !== null) {
+ if ($objectType->objectTypeID !== $objectTypeID) {
+ throw new IllegalLinkException();
+ }
+ }
+
+ $commentProcessor = $objectType->getProcessor();
+ if (!$commentProcessor->isAccessible($comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ if ($comment->isDisabled && !$commentProcessor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ private function assertResponseIsAccessible(Comment $comment, CommentResponse $response): void
+ {
+ $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
+ $commentProcessor = $objectType->getProcessor();
+
+ if ($response->commentID != $comment->commentID) {
+ throw new PermissionDeniedException();
+ }
+ if ($response->isDisabled && !$commentProcessor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ private function markNotificationsAsRead(Comment $comment, ?CommentResponse $response = null)
+ {
+ $objectType = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->objectType;
+ if ($response === null) {
+ CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
+ $objectType,
+ [new StructuredComment($comment)]
+ );
+ } else {
+ CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
+ $objectType,
+ [$response]
+ );
+ }
+ }
+
+ private function renderComment(Comment $comment, ?CommentResponse $response = null, bool $messageOnly = false): array
+ {
+ if ($comment->hasEmbeddedObjects) {
+ MessageEmbeddedObjectManager::getInstance()->loadObjects(
+ 'com.woltlab.wcf.comment',
+ [$comment->getObjectID()]
+ );
+ }
+
+ if ($messageOnly) {
+ $returnValue = [
+ 'template' => $comment->getFormattedMessage(),
+ ];
+
+ if ($response !== null) {
+ $returnValue['response'] = $this->renderResponse($response, $messageOnly);
+ }
+
+ return $returnValue;
+ }
+
+ $commentProcessor = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+
+ $structuredComment = new StructuredComment($comment);
+ $structuredComment->setIsDeletable($commentProcessor->canDeleteComment($comment));
+ $structuredComment->setIsEditable($commentProcessor->canEditComment($comment));
+
+ if ($response !== null) {
+ // check if response is not visible
+ /** @var CommentResponse $visibleResponse */
+ foreach ($comment as $visibleResponse) {
+ if ($visibleResponse->responseID == $response->responseID) {
+ $response = null;
+ break;
+ }
+ }
+ }
+
+ // This functions renders a single comment without rendering its responses.
+ // We need to prevent the setting of the data attribute for the last response time
+ // so that the loading of the responses by the user works correctly.
+ if ($comment->responses) {
+ WCF::getTPL()->assign('ignoreLastResponseTime', true);
+ }
+
+ WCF::getTPL()->assign([
+ 'commentCanAdd' => $commentProcessor->canAdd(
+ $comment->objectID
+ ),
+ 'commentCanModerate' => $commentProcessor->canModerate(
+ $comment->objectTypeID,
+ $comment->objectID
+ ),
+ 'commentList' => [$structuredComment],
+ 'commentManager' => $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 ($structuredComment 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);
+ }
+
+ $returnValue = [
+ 'template' => WCF::getTPL()->fetch('commentList'),
+ ];
+ if ($response !== null) {
+ $returnValue['response'] = $this->renderResponse($response);
+ }
+
+ return $returnValue;
+ }
+
+ private function renderResponse(CommentResponse $response, bool $messageOnly = false): string
+ {
+ if ($response->hasEmbeddedObjects) {
+ MessageEmbeddedObjectManager::getInstance()->loadObjects(
+ 'com.woltlab.wcf.comment.response',
+ [$response->getObjectID()]
+ );
+ }
+
+ if ($messageOnly) {
+ return $response->getFormattedMessage();
+ }
+
+ $commentProcessor = ObjectTypeCache::getInstance()->getObjectType($response->getComment()->objectTypeID)->getProcessor();
+
+ $structedResponse = new StructuredCommentResponse($response);
+ $structedResponse->setIsDeletable($commentProcessor->canDeleteResponse($response));
+ $structedResponse->setIsEditable($commentProcessor->canEditResponse($response));
+
+ return WCF::getTPL()->fetch('commentResponseList', 'wcf', [
+ 'responseList' => [$structedResponse],
+ 'commentCanModerate' => $commentProcessor->canModerate(
+ $response->getComment()->objectTypeID,
+ $response->getComment()->objectID
+ ),
+ 'commentManager' => $commentProcessor,
+ ]);
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\StructuredCommentList;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for loading additional rendered comments.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/render')]
+final class RenderComments implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $parameters = Helper::mapApiParameters($request, RenderCommentsParameters::class);
+ $objectType = CommentHandler::getInstance()->getObjectType($parameters->objectTypeID);
+ if ($objectType === null) {
+ throw new UserInputException('objectTypeID');
+ }
+
+ if (!$objectType->getProcessor()->isAccessible($parameters->objectID)) {
+ throw new PermissionDeniedException();
+ }
+
+ $commentList = $this->getCommentList(
+ $parameters->objectTypeID,
+ $parameters->objectID,
+ $parameters->lastCommentTime
+ );
+
+ CommentHandler::getInstance()->markNotificationsAsConfirmedForComments(
+ $objectType->objectType,
+ $commentList->getObjects()
+ );
+
+ return new JsonResponse([
+ 'lastCommentTime' => $commentList->getMinCommentTime(),
+ 'template' => WCF::getTPL()->fetch('commentList', 'wcf', [
+ 'commentList' => $commentList,
+ 'likeData' => MODULE_LIKE ? $commentList->getLikeData() : [],
+ ]),
+ ]);
+ }
+
+ private function getCommentList(int $objectTypeID, int $objectID, int $lastCommentTime): StructuredCommentList
+ {
+ $commentList = CommentHandler::getInstance()->getCommentList(
+ CommentHandler::getInstance()->getObjectType($objectTypeID)->getProcessor(),
+ $objectTypeID,
+ $objectID,
+ false
+ );
+ if ($lastCommentTime) {
+ $commentList->getConditionBuilder()->add("comment.time < ?", [$lastCommentTime]);
+ }
+ $commentList->readObjects();
+
+ return $commentList;
+ }
+}
+
+/** @internal */
+final class RenderCommentsParameters
+{
+ public function __construct(
+ /** @var positive-int **/
+ public readonly int $objectID,
+
+ /** @var positive-int **/
+ public readonly int $objectTypeID,
+
+ public readonly int $lastCommentTime = 0,
+ ) {
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\comment\CommentHandler;
+use wcf\system\exception\UserInputException;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\WCF;
+use wcf\util\MessageUtil;
+
+/**
+ * Trait that provides helper methods for comment controllers.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+trait TCommentMessageValidator
+{
+ private function validateMessage(string $message, bool $isResponse = false, int $objectID = 0): HtmlInputProcessor
+ {
+ $message = MessageUtil::stripCrap($message);
+ if ($message === '') {
+ throw new UserInputException('message');
+ }
+
+ CommentHandler::enforceCensorship($message);
+
+ BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+ ',',
+ WCF::getSession()->getPermission('user.comment.disallowedBBCodes')
+ ));
+
+ $htmlInputProcessor = new HtmlInputProcessor();
+ if ($isResponse) {
+ $htmlInputProcessor->process(
+ $message,
+ 'com.woltlab.wcf.comment.response',
+ $objectID
+ );
+ } else {
+ $htmlInputProcessor->process(
+ $message,
+ 'com.woltlab.wcf.comment',
+ $objectID
+ );
+ }
+
+ // 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');
+ }
+
+ $commentTextContent = $htmlInputProcessor->getTextContent();
+ if (\mb_strlen($commentTextContent) > WCF::getSession()->getPermission('user.comment.maxLength')) {
+ throw new UserInputException(
+ 'text',
+ WCF::getLanguage()->getDynamicVariable(
+ 'wcf.message.error.tooLong',
+ ['maxTextLength' => WCF::getSession()->getPermission('user.comment.maxLength')]
+ )
+ );
+ }
+
+ return $htmlInputProcessor;
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\event\message\MessageSpamChecking;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * API endpoint for the update of comments.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments/{id:\d+}')]
+final class UpdateComment implements IController
+{
+ use TCommentMessageValidator;
+
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $comment = Helper::fetchObjectFromRequestParameter($variables['id'], Comment::class);
+
+ $this->assertCommentIsEditable($comment);
+
+ $parameters = Helper::mapApiParameters($request, UpdateCommentParameters::class);
+
+ $htmlInputProcessor = $this->validateMessage($parameters->message, false, $comment->commentID);
+
+ $event = new MessageSpamChecking(
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ throw new PermissionDeniedException();
+ }
+
+ (new \wcf\system\comment\command\UpdateComment(
+ $comment,
+ $htmlInputProcessor,
+ ))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertCommentIsEditable(Comment $comment): void
+ {
+ $processor = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+ \assert($processor instanceof ICommentManager);
+ if (!$processor->canEditComment($comment)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
+
+/** @internal */
+final class UpdateCommentParameters
+{
+ public function __construct(
+ /** @var non-empty-string */
+ public readonly string $message,
+ ) {
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\data\object\type\ObjectType;
+use wcf\event\message\MessageSpamChecking;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\controller\core\comments\TCommentMessageValidator;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\NamedUserException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\exception\UserInputException;
+use wcf\system\flood\FloodControl;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * API endpoint for the creation of new responses.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments/responses')]
+final class CreateResponse implements IController
+{
+ use TCommentMessageValidator;
+
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ try {
+ CommentHandler::enforceFloodControl();
+ } catch (NamedUserException $e) {
+ throw new UserInputException('message', $e->getMessage());
+ }
+
+ $parameters = Helper::mapApiParameters($request, CreateResponseParameters::class);
+ $comment = Helper::fetchObjectFromRequestParameter($parameters->commentID, Comment::class);
+ $objectType = CommentHandler::getInstance()->getObjectType($comment->objectTypeID);
+
+ $this->assertResponseIsPossible($objectType, $comment);
+
+ $username = '';
+ if (!WCF::getUser()->userID) {
+ $username = UserUtil::verifyGuestToken($parameters->guestToken);
+ if ($username === null) {
+ throw new UserInputException('guestToken');
+ }
+ }
+
+ $isDisabled = !$objectType->getProcessor()->canAddWithoutApproval($comment->objectID);
+
+ $htmlInputProcessor = $this->validateMessage($parameters->message, true);
+
+ $event = new MessageSpamChecking(
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ $isDisabled = true;
+ }
+
+ $response = (new \wcf\system\comment\response\command\CreateResponse(
+ $comment,
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ $username,
+ $isDisabled,
+ ))();
+
+ FloodControl::getInstance()->registerContent('com.woltlab.wcf.comment');
+
+ return new JsonResponse([
+ 'responseID' => $response->responseID,
+ ]);
+ }
+
+ private function assertResponseIsPossible(ObjectType $objectType, Comment $comment): void
+ {
+ $processor = $objectType->getProcessor();
+ assert($processor instanceof ICommentManager);
+ if (!$processor->canAdd($comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+
+ if ($comment->isDisabled && !$processor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
+
+/** @internal */
+final class CreateResponseParameters
+{
+ public function __construct(
+ /** @var positive-int **/
+ public readonly int $commentID,
+
+ /** @var non-empty-string */
+ public readonly string $message,
+
+ public readonly string $guestToken,
+ ) {
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\response\CommentResponse;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\DeleteRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * API endpoint for the deletion of responses.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[DeleteRequest('/core/comments/responses/{id:\d+}')]
+final class DeleteResponse implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $response = Helper::fetchObjectFromRequestParameter($variables['id'], CommentResponse::class);
+
+ $this->assertResponseIsDeletable($response);
+
+ (new \wcf\system\comment\response\command\DeleteResponses([$response]))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertResponseIsDeletable(CommentResponse $response): void
+ {
+ $manager = CommentHandler::getInstance()->getCommentManagerByID($response->getComment()->objectTypeID);
+ if (!$manager->canDeleteResponse($response)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\response\CommentResponse;
+use wcf\http\Helper;
+use wcf\system\bbcode\BBCodeHandler;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\html\upcast\HtmlUpcastProcessor;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for starting the editing of a comment response.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/responses/{id:\d+}/edit')]
+final class EditResponse implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $response = Helper::fetchObjectFromRequestParameter($variables['id'], CommentResponse::class);
+
+ $this->assertResponseIsEditable($response);
+
+ BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+ ',',
+ WCF::getSession()->getPermission('user.comment.disallowedBBCodes')
+ ));
+
+ $upcastProcessor = new HtmlUpcastProcessor();
+ $upcastProcessor->process($response->message, 'com.woltlab.wcf.comment.response');
+
+ return new JsonResponse([
+ 'template' => WCF::getTPL()->fetch('commentResponseEditor', 'wcf', [
+ 'response' => $response,
+ 'text' => $upcastProcessor->getHtml(),
+ 'wysiwygSelector' => 'commentResponseEditor' . $response->responseID,
+ ]),
+ ]);
+ }
+
+ private function assertResponseIsEditable(CommentResponse $response): void
+ {
+ $manager = CommentHandler::getInstance()->getCommentManagerByID($response->getComment()->objectTypeID);
+ if (!$manager->canEditResponse($response)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\response\CommentResponse;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+
+/**
+ * API endpoint for enabling responses.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments/responses/{id:\d+}/enable')]
+final class EnableResponse implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $response = Helper::fetchObjectFromRequestParameter($variables['id'], CommentResponse::class);
+
+ $this->assertResponseCanBeEnabled($response);
+
+ (new \wcf\system\comment\response\command\PublishResponse($response))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertResponseCanBeEnabled(CommentResponse $response): void
+ {
+ if (!$response->isDisabled) {
+ throw new IllegalLinkException();
+ }
+
+ $comment = $response->getComment();
+ $processor = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+ \assert($processor instanceof ICommentManager);
+ if (!$processor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\response\CommentResponse;
+use wcf\data\comment\response\StructuredCommentResponse;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for the rendering of a single comment response.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/responses/{id:\d+}/render')]
+final class RenderResponse implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $response = Helper::fetchObjectFromRequestParameter($variables['id'], CommentResponse::class);
+ $parameters = Helper::mapQueryParameters(
+ $request->getQueryParams(),
+ <<<'EOT'
+ array {
+ messageOnly: null|bool,
+ objectTypeID: null|positive-int,
+ }
+ EOT,
+ );
+
+ $this->assertResponseIsAccessible($response, $parameters['objectTypeID']);
+ $this->markNotificationsAsRead($response);
+
+ return new JsonResponse([
+ 'template' => $this->renderResponse($response, $parameters['messageOnly'] ?? false),
+ ]);
+ }
+
+ private function assertResponseIsAccessible(CommentResponse $response, ?int $objectTypeID = null): void
+ {
+ $comment = $response->getComment();
+ $objectType = ObjectTypeCache::getInstance()->getObjectType($comment->objectTypeID);
+ if ($objectTypeID !== null) {
+ if ($objectType->objectTypeID !== $objectTypeID) {
+ throw new IllegalLinkException();
+ }
+ }
+ $commentProcessor = $objectType->getProcessor();
+
+ if (!$commentProcessor->isAccessible($comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ if ($response->commentID != $comment->commentID) {
+ throw new PermissionDeniedException();
+ }
+ if ($response->isDisabled && !$commentProcessor->canModerate($comment->objectTypeID, $comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+ }
+
+ private function markNotificationsAsRead(CommentResponse $response): void
+ {
+ $objectType = CommentHandler::getInstance()->getObjectType($response->getComment()->objectTypeID)->objectType;
+ CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
+ $objectType,
+ [$response]
+ );
+ }
+
+ private function renderResponse(CommentResponse $response, bool $messageOnly = false): string
+ {
+ if ($response->hasEmbeddedObjects) {
+ MessageEmbeddedObjectManager::getInstance()->loadObjects(
+ 'com.woltlab.wcf.comment.response',
+ [$response->getObjectID()]
+ );
+ }
+
+ if ($messageOnly) {
+ return $response->getFormattedMessage();
+ }
+
+ $commentProcessor = ObjectTypeCache::getInstance()->getObjectType($response->getComment()->objectTypeID)->getProcessor();
+
+ $structedResponse = new StructuredCommentResponse($response);
+ $structedResponse->setIsDeletable($commentProcessor->canDeleteResponse($response));
+ $structedResponse->setIsEditable($commentProcessor->canEditResponse($response));
+
+ return WCF::getTPL()->fetch('commentResponseList', 'wcf', [
+ 'responseList' => [$structedResponse],
+ 'commentCanModerate' => $commentProcessor->canModerate(
+ $response->getComment()->objectTypeID,
+ $response->getComment()->objectID
+ ),
+ 'commentManager' => $commentProcessor,
+ ]);
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\Comment;
+use wcf\data\comment\response\StructuredCommentResponseList;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\comment\manager\ICommentManager;
+use wcf\system\endpoint\GetRequest;
+use wcf\system\endpoint\IController;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\WCF;
+
+/**
+ * API endpoint for loading additional rendered responses.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[GetRequest('/core/comments/responses/render')]
+final class RenderResponses implements IController
+{
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $parameters = Helper::mapApiParameters($request, RenderReponsesParameters::class);
+ $comment = Helper::fetchObjectFromRequestParameter($parameters->commentID, Comment::class);
+ $commentManager = CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->getProcessor();
+ assert($commentManager instanceof ICommentManager);
+
+ if (!$commentManager->isAccessible($comment->objectID)) {
+ throw new PermissionDeniedException();
+ }
+
+ $commentCanModerate = $commentManager->canModerate(
+ $comment->objectTypeID,
+ $comment->objectID
+ );
+
+ // get response list
+ $responseList = new StructuredCommentResponseList($commentManager, $comment);
+ if ($parameters->lastResponseID) {
+ $responseList->getConditionBuilder()->add(
+ "(comment_response.time > ? OR (comment_response.time = ? && comment_response.responseID > ?))",
+ [
+ $parameters->lastResponseTime,
+ $parameters->lastResponseTime,
+ $parameters->lastResponseID,
+ ]
+ );
+ } else {
+ $responseList->getConditionBuilder()->add(
+ "comment_response.time > ?",
+ [$parameters->lastResponseTime]
+ );
+ }
+ if (!$commentCanModerate) {
+ $responseList->getConditionBuilder()->add("comment_response.isDisabled = ?", [0]);
+ }
+ $responseList->readObjects();
+
+ $lastResponseTime = $lastResponseID = 0;
+ foreach ($responseList as $response) {
+ if (!$lastResponseTime) {
+ $lastResponseTime = $response->time;
+ }
+ if (!$lastResponseID) {
+ $lastResponseID = $response->responseID;
+ }
+
+ $lastResponseTime = \max($lastResponseTime, $response->time);
+ $lastResponseID = \max($lastResponseID, $response->responseID);
+ }
+
+ CommentHandler::getInstance()->markNotificationsAsConfirmedForResponses(
+ CommentHandler::getInstance()->getObjectType($comment->objectTypeID)->objectType,
+ $responseList->getObjects()
+ );
+
+ return new JsonResponse([
+ 'lastResponseTime' => $lastResponseTime,
+ 'lastResponseID' => $lastResponseID,
+ 'template' => WCF::getTPL()->fetch('commentResponseList', 'wcf', [
+ 'commentCanModerate' => $commentCanModerate,
+ 'likeData' => MODULE_LIKE ? $responseList->getLikeData() : [],
+ 'responseList' => $responseList,
+ 'commentManager' => $commentManager,
+ ]),
+ ]);
+ }
+}
+
+/** @internal */
+final class RenderReponsesParameters
+{
+ public function __construct(
+ /** @var positive-int **/
+ public readonly int $commentID,
+
+ /** @var positive-int **/
+ public readonly int $lastResponseTime,
+
+ /** @var positive-int **/
+ public readonly int $lastResponseID,
+
+ public readonly bool $loadAllResponses,
+ ) {
+ }
+}
--- /dev/null
+<?php
+
+namespace wcf\system\endpoint\controller\core\comments\responses;
+
+use Laminas\Diactoros\Response\JsonResponse;
+use Psr\Http\Message\ResponseInterface;
+use Psr\Http\Message\ServerRequestInterface;
+use wcf\data\comment\response\CommentResponse;
+use wcf\event\message\MessageSpamChecking;
+use wcf\http\Helper;
+use wcf\system\comment\CommentHandler;
+use wcf\system\endpoint\controller\core\comments\TCommentMessageValidator;
+use wcf\system\endpoint\IController;
+use wcf\system\endpoint\PostRequest;
+use wcf\system\event\EventHandler;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\WCF;
+use wcf\util\UserUtil;
+
+/**
+ * API endpoint for the update of responses.
+ *
+ * @author Marcel Werk
+ * @copyright 2001-2024 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @since 6.1
+ */
+#[PostRequest('/core/comments/responses/{id:\d+}')]
+final class UpdateResponse implements IController
+{
+ use TCommentMessageValidator;
+
+ #[\Override]
+ public function __invoke(ServerRequestInterface $request, array $variables): ResponseInterface
+ {
+ $response = Helper::fetchObjectFromRequestParameter($variables['id'], CommentResponse::class);
+
+ $this->assertResponseIsEditable($response);
+
+ $parameters = Helper::mapApiParameters($request, UpdateCommentParameters::class);
+
+ $htmlInputProcessor = $this->validateMessage($parameters->message, true, $response->responseID);
+
+ $event = new MessageSpamChecking(
+ $htmlInputProcessor,
+ WCF::getUser()->userID ? WCF::getUser() : null,
+ UserUtil::getIpAddress(),
+ );
+ EventHandler::getInstance()->fire($event);
+ if ($event->defaultPrevented()) {
+ throw new PermissionDeniedException();
+ }
+
+ (new \wcf\system\comment\response\command\UpdateResponse(
+ $response,
+ $htmlInputProcessor,
+ ))();
+
+ return new JsonResponse([]);
+ }
+
+ private function assertResponseIsEditable(CommentResponse $response): void
+ {
+ $manager = CommentHandler::getInstance()->getCommentManagerByID($response->getComment()->objectTypeID);
+ if (!$manager->canEditResponse($response)) {
+ throw new PermissionDeniedException();
+ }
+ }
+}
+
+/** @internal */
+final class UpdateCommentParameters
+{
+ public function __construct(
+ /** @var non-empty-string */
+ public readonly string $message,
+ ) {
+ }
+}