diff --git a/wcfsetup/install/files/lib/acp/action/CacheClearAction.class.php b/wcfsetup/install/files/lib/acp/action/CacheClearAction.class.php index a47fe7b587b..0866f2c6a7b 100644 --- a/wcfsetup/install/files/lib/acp/action/CacheClearAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/CacheClearAction.class.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ResponseInterface; use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; -use wcf\system\cache\command\ClearCache; +use wcf\command\cache\ClearCache; use wcf\system\exception\PermissionDeniedException; use wcf\system\WCF; diff --git a/wcfsetup/install/files/lib/acp/action/DashboardConfigureAction.class.php b/wcfsetup/install/files/lib/acp/action/DashboardConfigureAction.class.php index d05ff336c79..135c67a5235 100644 --- a/wcfsetup/install/files/lib/acp/action/DashboardConfigureAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/DashboardConfigureAction.class.php @@ -7,7 +7,7 @@ use Psr\Http\Message\ServerRequestInterface; use Psr\Http\Server\RequestHandlerInterface; use wcf\system\acp\dashboard\AcpDashboard; -use wcf\system\acp\dashboard\command\ConfigureBoxes; +use wcf\command\acp\dashboard\ConfigureBoxes; use wcf\system\exception\PermissionDeniedException; use wcf\system\form\builder\field\MultipleSelectionFormField; use wcf\system\form\builder\Psr15DialogForm; diff --git a/wcfsetup/install/files/lib/acp/action/StyleAddDarkModeAction.class.php b/wcfsetup/install/files/lib/acp/action/StyleAddDarkModeAction.class.php index 5c2646d4fec..b4402c7f82c 100644 --- a/wcfsetup/install/files/lib/acp/action/StyleAddDarkModeAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/StyleAddDarkModeAction.class.php @@ -11,7 +11,6 @@ use wcf\http\Helper; use wcf\system\exception\IllegalLinkException; use wcf\system\exception\PermissionDeniedException; -use wcf\system\style\command\AddDarkMode; use wcf\system\WCF; /** @@ -51,7 +50,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface if ($request->getMethod() === 'GET') { return new TextResponse('Unsupported', 400); } elseif ($request->getMethod() === 'POST') { - $command = new AddDarkMode($style); + $command = new \wcf\command\style\AddDarkMode($style); $command(); return new EmptyResponse(); diff --git a/wcfsetup/install/files/lib/acp/action/TagSynonymAction.class.php b/wcfsetup/install/files/lib/acp/action/TagSynonymAction.class.php index b458bcd4857..221f096bb49 100644 --- a/wcfsetup/install/files/lib/acp/action/TagSynonymAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/TagSynonymAction.class.php @@ -15,7 +15,7 @@ use wcf\system\form\builder\container\FormContainer; use wcf\system\form\builder\field\RadioButtonFormField; use wcf\system\form\builder\Psr15DialogForm; -use wcf\system\tagging\command\SetSynonym; +use wcf\command\tag\SetTagSynonym; use wcf\system\WCF; /** @@ -70,7 +70,7 @@ public function handle(ServerRequestInterface $request): ResponseInterface } $tagID = $form->getData()["data"]["tagID"]; - (new SetSynonym( + (new SetTagSynonym( $tagList->search($tagID), $tagList->getObjects() ))(); diff --git a/wcfsetup/install/files/lib/acp/action/ToggleArticleI18nAction.class.php b/wcfsetup/install/files/lib/acp/action/ToggleArticleI18nAction.class.php index 85825536d89..12254e43f3d 100644 --- a/wcfsetup/install/files/lib/acp/action/ToggleArticleI18nAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/ToggleArticleI18nAction.class.php @@ -9,8 +9,8 @@ use Psr\Http\Server\RequestHandlerInterface; use wcf\data\article\Article; use wcf\http\Helper; -use wcf\system\article\command\DisableI18n; -use wcf\system\article\command\EnableI18n; +use wcf\command\article\DisableI18n; +use wcf\command\article\EnableI18n; use wcf\system\exception\IllegalLinkException; use wcf\system\form\builder\field\RadioButtonFormField; use wcf\system\form\builder\LanguageItemFormNode; diff --git a/wcfsetup/install/files/lib/acp/action/UserGroupCopyAction.class.php b/wcfsetup/install/files/lib/acp/action/UserGroupCopyAction.class.php index a77b10ab0fb..cbb2e55f9b4 100644 --- a/wcfsetup/install/files/lib/acp/action/UserGroupCopyAction.class.php +++ b/wcfsetup/install/files/lib/acp/action/UserGroupCopyAction.class.php @@ -15,7 +15,7 @@ use wcf\system\form\builder\field\BooleanFormField; use wcf\system\form\builder\Psr15DialogForm; use wcf\system\request\LinkHandler; -use wcf\system\user\group\command\CopyUserGroup; +use wcf\command\user\group\CopyUserGroup; use wcf\system\WCF; /** diff --git a/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php b/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php index 1aedd29006a..a93542ea619 100755 --- a/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php +++ b/wcfsetup/install/files/lib/acp/form/UserEditForm.class.php @@ -19,9 +19,8 @@ use wcf\system\html\upcast\HtmlUpcastProcessor; use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; use wcf\system\moderation\queue\ModerationQueueManager; -use wcf\system\option\user\UserOptionHandler; use wcf\system\style\StyleHandler; -use wcf\system\user\command\SetColorScheme; +use wcf\command\user\SetColorScheme; use wcf\system\user\multifactor\Setup; use wcf\system\WCF; use wcf\util\StringUtil; diff --git a/wcfsetup/install/files/lib/action/ModerationQueueAssignUserAction.class.php b/wcfsetup/install/files/lib/action/ModerationQueueAssignUserAction.class.php index 1742a660145..cbb940bc2dc 100644 --- a/wcfsetup/install/files/lib/action/ModerationQueueAssignUserAction.class.php +++ b/wcfsetup/install/files/lib/action/ModerationQueueAssignUserAction.class.php @@ -11,7 +11,7 @@ use wcf\system\form\builder\field\validation\FormFieldValidationError; use wcf\system\form\builder\field\validation\FormFieldValidator; use wcf\system\form\builder\Psr15DialogForm; -use wcf\system\moderation\queue\command\AssignUser; +use wcf\command\moderation\queue\AssignUser; use wcf\system\WCF; /** diff --git a/wcfsetup/install/files/lib/action/UserAvatarAction.class.php b/wcfsetup/install/files/lib/action/UserAvatarAction.class.php index 23981aa7532..62316ee0331 100644 --- a/wcfsetup/install/files/lib/action/UserAvatarAction.class.php +++ b/wcfsetup/install/files/lib/action/UserAvatarAction.class.php @@ -18,7 +18,7 @@ use wcf\system\form\builder\field\RadioButtonFormField; use wcf\system\form\builder\IFormDocument; use wcf\system\form\builder\Psr15DialogForm; -use wcf\system\user\command\SetAvatar; +use wcf\command\user\SetAvatar; use wcf\system\user\UserProfileHandler; use wcf\system\WCF; use wcf\util\HtmlString; diff --git a/wcfsetup/install/files/lib/action/UserFollowAction.class.php b/wcfsetup/install/files/lib/action/UserFollowAction.class.php index 7cf6fafa965..085aa077dc5 100644 --- a/wcfsetup/install/files/lib/action/UserFollowAction.class.php +++ b/wcfsetup/install/files/lib/action/UserFollowAction.class.php @@ -11,8 +11,8 @@ use wcf\http\Helper; use wcf\system\exception\IllegalLinkException; use wcf\system\exception\PermissionDeniedException; -use wcf\system\user\command\Follow; -use wcf\system\user\command\Unfollow; +use wcf\command\user\Follow; +use wcf\command\user\Unfollow; use wcf\system\WCF; /** diff --git a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php index c2fdf0b96b3..c903d76b659 100644 --- a/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php +++ b/wcfsetup/install/files/lib/bootstrap/com.woltlab.wcf.php @@ -3,7 +3,7 @@ use wcf\system\cronjob\CronjobScheduler; use wcf\system\event\EventHandler; use wcf\system\language\LanguageFactory; -use wcf\system\language\preload\command\ResetPreloadCache; +use wcf\command\language\preload\ResetPreloadCache; use wcf\system\language\preload\PhrasePreloader; use wcf\system\package\license\LicenseApi; use wcf\system\user\authentication\LoginRedirect; diff --git a/wcfsetup/install/files/lib/command/acp/dashboard/ConfigureBoxes.class.php b/wcfsetup/install/files/lib/command/acp/dashboard/ConfigureBoxes.class.php new file mode 100644 index 00000000000..74dd08cebb6 --- /dev/null +++ b/wcfsetup/install/files/lib/command/acp/dashboard/ConfigureBoxes.class.php @@ -0,0 +1,68 @@ + + * @since 6.2 + */ +final class ConfigureBoxes +{ + /** + * @param string[] $boxes + */ + public function __construct( + private readonly AcpDashboard $dashboard, + private readonly User $user, + private readonly array $boxes, + ) {} + + public function __invoke(): void + { + $this->resetBoxes(); + $this->saveBoxes(); + } + + private function resetBoxes(): void + { + $sql = "DELETE FROM wcf1_acp_dashboard_box_to_user WHERE userID = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$this->user->userID]); + } + + private function saveBoxes(): void + { + $sql = "INSERT INTO wcf1_acp_dashboard_box_to_user (boxName, userID, enabled, showOrder) VALUES (?, ?, ?, ?)"; + $statement = WCF::getDB()->prepare($sql); + $showOrder = 0; + foreach ($this->boxes as $boxName) { + $statement->execute([ + $boxName, + $this->user->userID, + 1, + $showOrder++ + ]); + } + + foreach ($this->dashboard->getBoxes() as $box) { + if (\in_array($box->getName(), $this->boxes)) { + continue; + } + + $statement->execute([ + $box->getName(), + $this->user->userID, + 0, + 0 + ]); + } + } +} diff --git a/wcfsetup/install/files/lib/system/article/command/DisableI18n.class.php b/wcfsetup/install/files/lib/command/article/DisableI18n.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/article/command/DisableI18n.class.php rename to wcfsetup/install/files/lib/command/article/DisableI18n.class.php index fb8d7820e06..c195095689a 100644 --- a/wcfsetup/install/files/lib/system/article/command/DisableI18n.class.php +++ b/wcfsetup/install/files/lib/command/article/DisableI18n.class.php @@ -1,6 +1,6 @@ + * @since 6.2 + */ +final class CreateBoxCondition +{ + /** + * @param mixed[] $conditionData + */ + public function __construct( + private readonly string $boxIdentifier, + private readonly string $conditionDefinition, + private readonly string $conditionObjectType, + private readonly array $conditionData + ) {} + + /** + * @throws \InvalidArgumentException + */ + public function __invoke(): void + { + $objectTypeID = $this->getObjectTypeID(); + if (!$objectTypeID) { + throw new \InvalidArgumentException( + "Unknown box condition '{$this->conditionObjectType}' of condition definition '{$this->conditionDefinition}'" + ); + } + + $box = Box::getBoxByIdentifier($this->boxIdentifier); + if ($box === null) { + throw new \InvalidArgumentException("Unknown box with identifier '{$this->boxIdentifier}'"); + } + + (new ConditionAction([], 'create', [ + 'data' => [ + 'conditionData' => \serialize($this->conditionData), + 'objectID' => $box->boxID, + 'objectTypeID' => $objectTypeID, + ], + ]))->executeAction(); + } + + private function getObjectTypeID(): ?int + { + // do not rely on caches during package installation + $sql = "SELECT objectTypeID + FROM wcf1_object_type object_type + INNER JOIN wcf1_object_type_definition object_type_definition + ON object_type.definitionID = object_type_definition.definitionID + WHERE objectType = ? + AND definitionName = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$this->conditionObjectType, $this->conditionDefinition]); + + return $statement->fetchSingleColumn(); + } +} diff --git a/wcfsetup/install/files/lib/command/box/CreateBoxToPageAssignments.class.php b/wcfsetup/install/files/lib/command/box/CreateBoxToPageAssignments.class.php new file mode 100644 index 00000000000..b162391cadb --- /dev/null +++ b/wcfsetup/install/files/lib/command/box/CreateBoxToPageAssignments.class.php @@ -0,0 +1,67 @@ + + * @since 6.2 + */ +final class CreateBoxToPageAssignments +{ + /** + * @param string[] $pageIdentifiers + */ + public function __construct( + private readonly string $boxIdentifier, + private readonly array $pageIdentifiers, + private readonly bool $visible = true, + ) {} + + /** + * @throws \InvalidArgumentException + */ + public function __invoke(): void + { + $box = Box::getBoxByIdentifier($this->boxIdentifier); + if ($box === null) { + throw new \InvalidArgumentException("Unknown box with identifier '{$this->boxIdentifier}'"); + } + + $pages = []; + foreach ($this->pageIdentifiers as $pageIdentifier) { + $page = Page::getPageByIdentifier($pageIdentifier); + if ($page === null) { + throw new \InvalidArgumentException("Unknown page with identifier '{$pageIdentifier}'"); + } + $pages[] = $page; + } + + if (($this->visible && $box->visibleEverywhere) || (!$this->visible && !$box->visibleEverywhere)) { + $sql = "DELETE FROM wcf1_box_to_page + WHERE boxID = ? + AND pageID = ?"; + $statement = WCF::getDB()->prepare($sql); + foreach ($pages as $page) { + $statement->execute([$box->boxID, $page->pageID]); + } + } else { + $sql = "REPLACE INTO wcf1_box_to_page + (boxID, pageID, visible) + VALUES (?, ?, ?)"; + $statement = WCF::getDB()->prepare($sql); + foreach ($pages as $page) { + $statement->execute([$box->boxID, $page->pageID, $this->visible ? 1 : 0]); + } + } + } +} diff --git a/wcfsetup/install/files/lib/command/cache/ClearCache.class.php b/wcfsetup/install/files/lib/command/cache/ClearCache.class.php new file mode 100644 index 00000000000..d2dd6d559ae --- /dev/null +++ b/wcfsetup/install/files/lib/command/cache/ClearCache.class.php @@ -0,0 +1,42 @@ + + * @since 6.2 + */ +final class ClearCache +{ + public function __invoke(): void + { + OptionEditor::resetCache(); + + UserStorageHandler::getInstance()->clear(); + + StyleHandler::resetStylesheets(); + + LanguageFactory::getInstance()->deleteLanguageCache(); + + CacheHandler::getInstance()->flushAll(); + + PackageUpdateServer::resetAll(); + + EventHandler::getInstance()->fire( + new CacheCleared() + ); + } +} diff --git a/wcfsetup/install/files/lib/command/comment/CreateComment.class.php b/wcfsetup/install/files/lib/command/comment/CreateComment.class.php new file mode 100644 index 00000000000..7965cc910ef --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/CreateComment.class.php @@ -0,0 +1,76 @@ + + * @since 6.2 + */ +final class CreateComment +{ + public function __construct( + private readonly ObjectType $objectType, + private readonly int $objectID, + private readonly HtmlInputProcessor $htmlInputProcessor, + private readonly ?User $user = null, + private readonly string $username = '', + private readonly bool $isDisabled = false, + ) {} + + public function __invoke(): Comment + { + $action = new CommentAction([], 'create', [ + 'data' => [ + 'objectTypeID' => $this->objectType->objectTypeID, + 'objectID' => $this->objectID, + 'time' => TIME_NOW, + 'userID' => $this->user ? $this->user->userID : null, + 'username' => $this->user ? $this->user->username : $this->username, + 'message' => $this->htmlInputProcessor->getHtml(), + 'responses' => 0, + 'responseIDs' => \serialize([]), + 'enableHtml' => 1, + 'isDisabled' => $this->isDisabled ? 1 : 0, + ] + ]); + $comment = $action->executeAction()['returnValues']; + \assert($comment instanceof Comment); + + $this->htmlInputProcessor->setObjectID($comment->getObjectID()); + if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor)) { + (new CommentEditor($comment))->update([ + 'hasEmbeddedObjects' => 1, + ]); + $comment = new Comment($comment->getObjectID()); + } + + if (!$comment->isDisabled) { + (new PublishComment($comment))(); + } else { + ModerationQueueActivationManager::getInstance()->addModeratedContent( + 'com.woltlab.wcf.comment.comment', + $comment->commentID + ); + } + + $event = new CommentCreated($comment); + EventHandler::getInstance()->fire($event); + + return $comment; + } +} diff --git a/wcfsetup/install/files/lib/command/comment/DeleteComments.class.php b/wcfsetup/install/files/lib/command/comment/DeleteComments.class.php new file mode 100644 index 00000000000..e1d57dcffeb --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/DeleteComments.class.php @@ -0,0 +1,141 @@ + + * @since 6.2 + */ +final class DeleteComments +{ + private readonly ObjectType $objectType; + private readonly ICommentManager $commentManager; + /** + * @var int[] + */ + private readonly array $commentIDs; + + /** + * @param Comment[] $comments + */ + public function __construct( + private readonly array $comments, + private readonly bool $updateCounters = true, + ) { + $this->commentIDs = \array_column($this->comments, 'commentID'); + $firstCommentID = \array_key_first($this->comments); + \assert($firstCommentID !== null); + $firstComment = $this->comments[$firstCommentID]; + $this->objectType = CommentHandler::getInstance()->getObjectType($firstComment->objectTypeID); + $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($firstComment->objectTypeID); + } + + public function __invoke(): void + { + $this->deleteActivityEvents(); + $this->deleteNotifications(); + $this->deleteReactions(); + $this->deleteModerationQueues(); + $this->deleteMessageEmbeddedObjects(); + $this->deleteResponses(); + + $action = new CommentAction($this->commentIDs, 'delete'); + $action->executeAction(); + + $this->updateCounters(); + + $event = new CommentsDeleted($this->comments); + EventHandler::getInstance()->fire($event); + } + + private function deleteActivityEvents(): void + { + if (UserActivityEventHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents( + $this->objectType->objectType . '.recentActivityEvent', + $this->commentIDs + ); + } + } + + private function deleteNotifications(): void + { + if (UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.notification')) { + UserNotificationHandler::getInstance()->removeNotifications( + $this->objectType->objectType . '.notification', + $this->commentIDs + ); + } + } + + private function deleteReactions(): void + { + ReactionHandler::getInstance()->removeReactions( + 'com.woltlab.wcf.comment', + $this->commentIDs, + UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.like.notification') + ? [$this->objectType->objectType . '.like.notification'] + : [] + ); + } + + private function deleteResponses(): void + { + $responseList = new CommentResponseList(); + $responseList->getConditionBuilder()->add('comment_response.commentID IN (?)', [$this->commentIDs]); + $responseList->readObjects(); + if (!\count($responseList)) { + return; + } + + (new DeleteResponses($responseList->getObjects(), $this->updateCounters))(); + } + + private function deleteModerationQueues(): void + { + ModerationQueueManager::getInstance()->removeQueues( + 'com.woltlab.wcf.comment.comment', + $this->commentIDs + ); + } + + private function deleteMessageEmbeddedObjects(): void + { + MessageEmbeddedObjectManager::getInstance()->removeObjects( + 'com.woltlab.wcf.comment', + $this->commentIDs + ); + } + + private function updateCounters(): void + { + if (!$this->updateCounters) { + return; + } + + foreach ($this->comments as $comment) { + if (!$comment->isDisabled) { + $this->commentManager->updateCounter($comment->objectID, -1); + } + } + } +} diff --git a/wcfsetup/install/files/lib/command/comment/PublishComment.class.php b/wcfsetup/install/files/lib/command/comment/PublishComment.class.php new file mode 100644 index 00000000000..86b434afdb9 --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/PublishComment.class.php @@ -0,0 +1,103 @@ + + * @since 6.2 + */ +final class PublishComment +{ + private readonly ObjectType $objectType; + private readonly ICommentManager $commentManager; + + public function __construct( + private readonly Comment $comment, + ) { + $this->objectType = CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID); + $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($this->comment->objectTypeID); + } + + public function __invoke(): void + { + if ($this->comment->isDisabled) { + (new CommentEditor($this->comment))->update([ + 'isDisabled' => 0 + ]); + } + $this->commentManager->updateCounter($this->comment->objectID, 1); + + $this->fireActivityEvent(); + $this->fireNotificationEvent(); + + $event = new CommentPublished($this->comment); + EventHandler::getInstance()->fire($event); + } + + private function fireActivityEvent(): void + { + if ( + $this->comment->userID + && UserActivityEventHandler::getInstance()->getObjectTypeID( + $this->objectType->objectType . '.recentActivityEvent' + ) + ) { + UserActivityEventHandler::getInstance()->fireEvent( + $this->objectType->objectType . '.recentActivityEvent', + $this->comment->commentID, + null, + $this->comment->userID, + $this->comment->time + ); + } + } + + private function fireNotificationEvent(): void + { + if (!UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.notification', 'comment')) { + return; + } + + $notificationObject = new CommentUserNotificationObject($this->comment); + $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor( + $this->objectType->objectType . '.notification' + ); + + if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) { + $recipientIDs = $notificationObjectType->getRecipientIDs($this->comment); + } else { + $recipientIDs = []; + } + + if ($notificationObjectType instanceof ICommentUserNotificationObjectType) { + $recipientIDs[] = $notificationObjectType->getOwnerID($this->comment->commentID); + } + + // make sure that the comment's author gets no notification + $recipientIDs = \array_diff($recipientIDs, [$this->comment->getUserID()]); + + UserNotificationHandler::getInstance()->fireEvent( + 'comment', + $this->objectType->objectType . '.notification', + $notificationObject, + $recipientIDs + ); + } +} diff --git a/wcfsetup/install/files/lib/command/comment/UpdateComment.class.php b/wcfsetup/install/files/lib/command/comment/UpdateComment.class.php new file mode 100644 index 00000000000..b2d63a7a48d --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/UpdateComment.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +final class UpdateComment +{ + public function __construct( + private readonly Comment $comment, + private readonly HtmlInputProcessor $htmlInputProcessor, + ) {} + + public function __invoke(): void + { + $data = [ + 'message' => $this->htmlInputProcessor->getHtml(), + ]; + + $this->htmlInputProcessor->setObjectID($this->comment->getObjectID()); + $hasEmbeddedObjects = MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor); + if ($this->comment->hasEmbeddedObjects != $hasEmbeddedObjects) { + $data['hasEmbeddedObjects'] = $this->comment->hasEmbeddedObjects ? 0 : 1; + } + + $action = new CommentAction([$this->comment], 'update', [ + 'data' => $data, + ]); + $action->executeAction(); + + $event = new CommentUpdated(new Comment($this->comment->commentID)); + EventHandler::getInstance()->fire($event); + } +} diff --git a/wcfsetup/install/files/lib/command/comment/response/CreateResponse.class.php b/wcfsetup/install/files/lib/command/comment/response/CreateResponse.class.php new file mode 100644 index 00000000000..45153326aa6 --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/response/CreateResponse.class.php @@ -0,0 +1,89 @@ + + * @since 6.2 + */ +final class CreateResponse +{ + public function __construct( + private readonly Comment $comment, + private readonly HtmlInputProcessor $htmlInputProcessor, + private readonly ?User $user = null, + private readonly string $username = '', + private readonly bool $isDisabled = false, + ) {} + + public function __invoke(): CommentResponse + { + $action = new CommentResponseAction([], 'create', [ + 'data' => [ + 'commentID' => $this->comment->commentID, + 'time' => TIME_NOW, + 'userID' => $this->user ? $this->user->userID : null, + 'username' => $this->user ? $this->user->username : $this->username, + 'message' => $this->htmlInputProcessor->getHtml(), + 'enableHtml' => 1, + 'isDisabled' => $this->isDisabled ? 1 : 0, + ] + ]); + $response = $action->executeAction()['returnValues']; + \assert($response instanceof CommentResponse); + + $this->updateResponseData($response); + + $this->htmlInputProcessor->setObjectID($response->getObjectID()); + if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor)) { + (new CommentResponseEditor($response))->update([ + 'hasEmbeddedObjects' => 1, + ]); + $response = new CommentResponse($response->getObjectID()); + } + + if (!$response->isDisabled) { + (new PublishResponse($response))(); + } else { + ModerationQueueActivationManager::getInstance()->addModeratedContent( + 'com.woltlab.wcf.comment.response', + $response->responseID + ); + } + + $event = new ResponseCreated($response); + EventHandler::getInstance()->fire($event); + + return $response; + } + + private function updateResponseData(CommentResponse $response): void + { + $unfilteredResponseIDs = $this->comment->getUnfilteredResponseIDs(); + if (\count($unfilteredResponseIDs) < 5) { + $unfilteredResponseIDs[] = $response->responseID; + } + $unfilteredResponses = $this->comment->unfilteredResponses + 1; + + (new CommentEditor($this->comment))->update([ + 'unfilteredResponseIDs' => \serialize($unfilteredResponseIDs), + 'unfilteredResponses' => $unfilteredResponses, + ]); + } +} diff --git a/wcfsetup/install/files/lib/command/comment/response/DeleteResponses.class.php b/wcfsetup/install/files/lib/command/comment/response/DeleteResponses.class.php new file mode 100644 index 00000000000..028defc1ad8 --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/response/DeleteResponses.class.php @@ -0,0 +1,148 @@ + + * @since 6.2 + */ +final class DeleteResponses +{ + private readonly ObjectType $objectType; + private readonly ICommentManager $commentManager; + + /** + * @var int[] + */ + private readonly array $responseIDs; + + /** + * @param CommentResponse[] $responses + */ + public function __construct( + private readonly array $responses, + private readonly bool $updateCounters = true, + ) { + $this->responseIDs = \array_column($this->responses, 'responseID'); + $firstResponseKey = \array_key_first($this->responses); + \assert($firstResponseKey !== null); + $firstResponse = $this->responses[$firstResponseKey]; + $this->objectType = CommentHandler::getInstance()->getObjectType($firstResponse->getComment()->objectTypeID); + $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($firstResponse->getComment()->objectTypeID); + } + + public function __invoke(): void + { + $this->deleteActivityEvents(); + $this->deleteNotifications(); + $this->deleteReactions(); + $this->deleteModerationQueues(); + $this->deleteMessageEmbeddedObjects(); + + $action = new CommentResponseAction($this->responseIDs, 'delete'); + $action->executeAction(); + + $this->updateCounters(); + + $event = new ResponsesDeleted($this->responses); + EventHandler::getInstance()->fire($event); + } + + private function deleteActivityEvents(): void + { + if (UserActivityEventHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.recentActivityEvent')) { + UserActivityEventHandler::getInstance()->removeEvents( + $this->objectType->objectType . '.response.recentActivityEvent', + $this->responseIDs + ); + } + } + + private function deleteNotifications(): void + { + if (UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.notification')) { + UserNotificationHandler::getInstance()->removeNotifications( + $this->objectType->objectType . '.response.notification', + $this->responseIDs + ); + } + } + + private function deleteReactions(): void + { + ReactionHandler::getInstance()->removeReactions( + 'com.woltlab.wcf.comment.response', + $this->responseIDs, + UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.like.notification') + ? [$this->objectType->objectType . '.response.like.notification'] + : [] + ); + } + + private function deleteModerationQueues(): void + { + ModerationQueueManager::getInstance()->removeQueues( + 'com.woltlab.wcf.comment.response', + $this->responseIDs + ); + } + + private function deleteMessageEmbeddedObjects(): void + { + MessageEmbeddedObjectManager::getInstance()->removeObjects( + 'com.woltlab.wcf.comment.response', + $this->responseIDs + ); + } + + private function updateCounters(): void + { + if (!$this->updateCounters) { + return; + } + + $commentIDs = []; + foreach ($this->responses as $response) { + $commentIDs[] = $response->commentID; + } + + $commentList = new CommentList(); + $commentList->setObjectIDs(\array_unique($commentIDs)); + $commentList->readObjects(); + ///** @var array */ + $comments = $commentList->getObjects(); + + foreach ($comments as $comment) { + $commentEditor = new CommentEditor($comment); + $commentEditor->updateResponseIDs(); + $commentEditor->updateUnfilteredResponseIDs(); + $commentEditor->updateResponses(); + $commentEditor->updateUnfilteredResponses(); + } + + foreach ($this->responses as $response) { + if (!$response->isDisabled) { + $this->commentManager->updateCounter($comments[$response->commentID]->objectID, -1); + } + } + } +} diff --git a/wcfsetup/install/files/lib/command/comment/response/PublishResponse.class.php b/wcfsetup/install/files/lib/command/comment/response/PublishResponse.class.php new file mode 100644 index 00000000000..148a3c8ff5b --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/response/PublishResponse.class.php @@ -0,0 +1,145 @@ + + * @since 6.2 + */ +final class PublishResponse +{ + private readonly ObjectType $objectType; + private readonly ICommentManager $commentManager; + private readonly Comment $comment; + + public function __construct( + private readonly CommentResponse $response, + ) { + $this->comment = $response->getComment(); + $this->objectType = CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID); + $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($this->comment->objectTypeID); + } + + public function __invoke(): void + { + if ($this->response->isDisabled) { + (new CommentResponseEditor($this->response))->update([ + 'isDisabled' => 0, + ]); + } + + $commentEditor = new CommentEditor($this->comment); + $commentEditor->updateCounters(['responses' => 1]); + // do not prepend the response id as the approved response can appear anywhere + $commentEditor->updateResponseIDs(); + + $this->commentManager->updateCounter($this->comment->objectID, 1); + + $this->fireActivityEvent(); + $this->fireNotificationEvent(); + + $event = new ResponsePublished($this->response); + EventHandler::getInstance()->fire($event); + } + + private function fireActivityEvent(): void + { + if ( + $this->response->userID + && UserActivityEventHandler::getInstance()->getObjectTypeID( + $this->objectType->objectType . '.response.recentActivityEvent' + ) + ) { + UserActivityEventHandler::getInstance()->fireEvent( + $this->objectType->objectType . '.response.recentActivityEvent', + $this->response->responseID, + null, + $this->response->userID, + $this->response->time + ); + } + } + + private function fireNotificationEvent(): void + { + if ( + !UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.notification') + || ( + !UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponse') + && !UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponseOwner') + ) + ) { + return; + } + + $notificationObject = new CommentResponseUserNotificationObject($this->response); + $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($this->objectType->objectType . '.notification'); + + if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) { + $recipientIDs = $notificationObjectType->getRecipientIDs($this->comment); + } else { + $recipientIDs = []; + } + + $recipientIDs[] = $this->comment->userID; + + $userID = 0; + if ($notificationObjectType instanceof ICommentUserNotificationObjectType) { + $userID = $notificationObjectType->getOwnerID($this->comment->commentID); + } + + // make sure that the response's author gets no notification + $recipientIDs = \array_diff($recipientIDs, [$this->response->getUserID()]); + + if (UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponse')) { + UserNotificationHandler::getInstance()->fireEvent( + 'commentResponse', + $this->objectType->objectType . '.response.notification', + $notificationObject, + $recipientIDs, + [ + 'commentID' => $this->comment->commentID, + 'objectID' => $this->comment->objectID, + 'userID' => $this->comment->userID, + ] + ); + } + + // notify the container owner + if (UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponseOwner')) { + if ($userID && $userID != $this->comment->userID && $userID != $this->response->getUserID()) { + UserNotificationHandler::getInstance()->fireEvent( + 'commentResponseOwner', + $this->objectType->objectType . '.response.notification', + $notificationObject, + [$userID], + [ + 'commentID' => $this->comment->commentID, + 'objectID' => $this->comment->objectID, + 'objectUserID' => $userID, + 'userID' => $this->comment->userID, + ] + ); + } + } + } +} diff --git a/wcfsetup/install/files/lib/command/comment/response/UpdateResponse.class.php b/wcfsetup/install/files/lib/command/comment/response/UpdateResponse.class.php new file mode 100644 index 00000000000..9ec83527144 --- /dev/null +++ b/wcfsetup/install/files/lib/command/comment/response/UpdateResponse.class.php @@ -0,0 +1,47 @@ + + * @since 6.2 + */ +final class UpdateResponse +{ + public function __construct( + private readonly CommentResponse $response, + private readonly HtmlInputProcessor $htmlInputProcessor, + ) {} + + public function __invoke(): void + { + $data = [ + 'message' => $this->htmlInputProcessor->getHtml(), + ]; + + $this->htmlInputProcessor->setObjectID($this->response->getObjectID()); + $hasEmbeddedObjects = MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor); + if ($this->response->hasEmbeddedObjects != $hasEmbeddedObjects) { + $data['hasEmbeddedObjects'] = $this->response->hasEmbeddedObjects ? 0 : 1; + } + + $action = new CommentResponseAction([$this->response], 'update', [ + 'data' => $data, + ]); + $action->executeAction(); + + $event = new ResponseUpdated(new CommentResponse($this->response->responseID)); + EventHandler::getInstance()->fire($event); + } +} diff --git a/wcfsetup/install/files/lib/command/file/ReplaceFileSource.class.php b/wcfsetup/install/files/lib/command/file/ReplaceFileSource.class.php new file mode 100644 index 00000000000..53f02e742c2 --- /dev/null +++ b/wcfsetup/install/files/lib/command/file/ReplaceFileSource.class.php @@ -0,0 +1,207 @@ + + * @since 6.2 + */ +final class ReplaceFileSource +{ + public function __construct( + private readonly File $file, + private readonly string $pathname, + private readonly ?string $filename = null, + private readonly ?bool $regenerateThumbnails = true, + ) {} + + public function __invoke(): File + { + $this->validatePathname($this->pathname); + $this->validatePathname($this->file->getPathname()); + $this->validatePathname($this->file->getPathnameWebp()); + + $file = $this->replaceSource(); + $file = $this->discardWebpVariantOfWebpFile($file); + + if ($this->regenerateThumbnails) { + $file = $this->regenerateExistingThumbnails($file); + } + + return $file; + } + + private function validatePathname(?string $pathname): void + { + if ($pathname === null) { + return; + } + + if (!\file_exists($pathname)) { + throw new \RuntimeException("The file '{$pathname}' does not exist."); + } + + if (!\is_writable($pathname)) { + throw new \RuntimeException("The file '{$pathname}' is not writable."); + } + } + + private function replaceSource(): File + { + $mimeType = FileUtil::getMimeType($this->pathname); + $isImage = match ($mimeType) { + 'image/gif' => true, + 'image/jpeg' => true, + 'image/png' => true, + 'image/webp' => true, + default => false, + }; + + $width = $height = null; + if ($isImage) { + [$width, $height] = \getimagesize($this->pathname); + } + + $filename = $this->filename ? $this->filename : \basename($this->pathname); + $fileSize = \filesize($this->pathname); + $fileHash = \hash_file('sha256', $this->pathname); + $fileExtension = File::getSafeFileExtension($mimeType, $filename); + + // The following code uses a transaction and a write lock on the file in + // order to guarantee this operation to atomic. The replacement is most + // likely the result of a post-processing step of the file thus is a + // repeatable action. + // + // If the rename fails for whatever reasons, then this file becomes + // corrupted without being able to recover the old state. This guard + // code prevents this from happening. + WCF::getDB()->beginTransaction(); + $committed = false; + try { + $sql = "SELECT * + FROM wcf1_file + WHERE fileID = ? + FOR UPDATE"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$this->file->fileID]); + + (new FileEditor($this->file))->update([ + 'filename' => $filename, + 'fileSize' => $fileSize, + 'fileHash' => $fileHash, + 'fileExtension' => $fileExtension, + 'mimeType' => $mimeType, + 'width' => $width, + 'height' => $height, + ]); + + $updatedFile = new File($this->file->fileID); + + $path = \dirname($updatedFile->getPathname()); + FileUtil::makePath($path); + + \rename( + $this->pathname, + $updatedFile->getPathname(), + ); + + // Remove the previous file source because the new filename could + // differ due to the checksums. + if ($this->file->getPathname() !== $updatedFile->getPathname()) { + \unlink($this->file->getPathname()); + } + + // Move the WebP thumbnail to the new location. + $webpVariant = $this->file->getPathnameWebp(); + if ($webpVariant !== null) { + if ($updatedFile->mimeType === 'image/webp') { + // The file might be missing if it was used to replace the + // original version with it. + @\unlink($webpVariant); + + (new FileEditor($updatedFile))->update([ + 'fileHashWebp' => null, + ]); + $updatedFile = new File($updatedFile->fileID); + } else { + $newWebpVariant = $updatedFile->getPathnameWebp(); + \assert($newWebpVariant !== null); + + \rename( + $webpVariant, + $newWebpVariant, + ); + } + } + + WCF::getDB()->commitTransaction(); + $committed = true; + + return $updatedFile; + } finally { + if (!$committed) { + WCF::getDB()->rollBackTransaction(); + } + } + } + + private function discardWebpVariantOfWebpFile(File $file): File + { + if ($file->mimeType !== 'image/webp') { + return $file; + } + + $pathname = $file->getPathnameWebp(); + if ($pathname === null) { + return $file; + } + + if (\file_exists($pathname)) { + \unlink($file->getPathnameWebp()); + } + + (new FileEditor($file))->update([ + 'fileHashWebp' => null, + ]); + + return new File($file->fileID); + } + + private function regenerateExistingThumbnails(File $file): File + { + $sql = "SELECT COUNT(*) + FROM wcf1_file_thumbnail + WHERE fileID = ?"; + $statement = WCF::getDB()->prepare($sql); + $statement->execute([$file->fileID]); + $count = $statement->fetchSingleColumn(); + if ($count === 0) { + return $file; + } + + try { + $file = FileProcessor::getInstance()->generateWebpVariant($file); + $file = FileProcessor::getInstance()->stripExif($file); + $file = FileProcessor::getInstance()->convertImageFormat($file); + FileProcessor::getInstance()->generateThumbnails($file); + } catch (DamagedImage $e) { + logThrowable($e); + } finally { + return $file; + } + } +} diff --git a/wcfsetup/install/files/lib/system/file/command/ReplaceWithWebpVariant.class.php b/wcfsetup/install/files/lib/command/file/ReplaceWithWebpVariant.class.php similarity index 97% rename from wcfsetup/install/files/lib/system/file/command/ReplaceWithWebpVariant.class.php rename to wcfsetup/install/files/lib/command/file/ReplaceWithWebpVariant.class.php index fec1fd9331d..4f66a62e844 100644 --- a/wcfsetup/install/files/lib/system/file/command/ReplaceWithWebpVariant.class.php +++ b/wcfsetup/install/files/lib/command/file/ReplaceWithWebpVariant.class.php @@ -1,6 +1,6 @@ + * @since 6.2 + */ +final class CachePreloadPhrases +{ + public function __construct( + private readonly Language $language + ) {} + + public function __invoke(): void + { + $event = new PreloadPhrasesCollecting($this->language); + EventHandler::getInstance()->fire($event); + + $file = new AtomicWriter(\WCF_DIR . $this->language->getPreloadCacheFilename()); + $file->write( + \sprintf( + "/* cache for '%s' (generated at %s) -- DO NOT EDIT */\n", + $this->language->getLocale(), + (new \DateTimeImmutable())->format('c'), + ) + ); + + foreach ($event->getPhrases() as $phrase) { + $file->write( + \sprintf( + "WoltLabLanguage.registerPhrase('%s', '%s');\n", + $phrase, + $this->getEncodedValue($phrase), + ) + ); + } + + // Add a distinct marker at the end to prove + // that the file was fully written. + $file->write("/* EOF */\n"); + + $file->flush(); + $file->close(); + } + + private function getEncodedValue(string $phrase): string + { + return StringUtil::encodeJS($this->language->get($phrase)); + } +} diff --git a/wcfsetup/install/files/lib/command/language/preload/ResetPreloadCache.class.php b/wcfsetup/install/files/lib/command/language/preload/ResetPreloadCache.class.php new file mode 100644 index 00000000000..03abaef6635 --- /dev/null +++ b/wcfsetup/install/files/lib/command/language/preload/ResetPreloadCache.class.php @@ -0,0 +1,29 @@ + + * @since 6.2 + */ +final class ResetPreloadCache +{ + public function __construct( + private readonly Language $language + ) {} + + public function __invoke(): void + { + // Try to remove the file if it exists. + $filename = \WCF_DIR . $this->language->getPreloadCacheFilename(); + if (\file_exists($filename)) { + \unlink($filename); + } + } +} diff --git a/wcfsetup/install/files/lib/command/moderation/queue/AssignUser.class.php b/wcfsetup/install/files/lib/command/moderation/queue/AssignUser.class.php new file mode 100644 index 00000000000..18dbf15eb9f --- /dev/null +++ b/wcfsetup/install/files/lib/command/moderation/queue/AssignUser.class.php @@ -0,0 +1,61 @@ + + * @since 6.2 + */ +final class AssignUser +{ + public function __construct( + private readonly ModerationQueue $moderationQueue, + private readonly ?User $user, + ) {} + + public function __invoke(): void + { + $moderationQueueEditor = new ModerationQueueEditor($this->moderationQueue); + $oldAssignee = $moderationQueueEditor->assignedUserID ? new User($moderationQueueEditor->assignedUserID) : null; + + // If the old assignee matches the new assignee, we do not need to + // do anything. + if ($oldAssignee?->userID === $this->user?->userID) { + return; + } + + $data = [ + 'assignedUserID' => $this->user?->userID, + ]; + + if ($this->user !== null) { + if ($moderationQueueEditor->status == ModerationQueue::STATUS_OUTSTANDING) { + $data['status'] = ModerationQueue::STATUS_PROCESSING; + } + } else { + if ($moderationQueueEditor->status == ModerationQueue::STATUS_PROCESSING) { + $data['status'] = ModerationQueue::STATUS_OUTSTANDING; + } + } + + $moderationQueueEditor->update($data); + + EventHandler::getInstance()->fire( + new UserAssigned( + $moderationQueueEditor->getDecoratedObject(), + $this->user, + $oldAssignee + ) + ); + } +} diff --git a/wcfsetup/install/files/lib/command/package/RebuildBootstrapper.class.php b/wcfsetup/install/files/lib/command/package/RebuildBootstrapper.class.php new file mode 100644 index 00000000000..a6d84cccb26 --- /dev/null +++ b/wcfsetup/install/files/lib/command/package/RebuildBootstrapper.class.php @@ -0,0 +1,92 @@ + + * @since 6.2 + */ +final class RebuildBootstrapper +{ + public function __invoke(): void + { + $groups = PackageList::getTopologicallySortedPackages(); + + $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); + + $result = "format('c')} */\n\n"; + $result .= <<<'EOT' + return (function() { + if (\ENABLE_DEBUG_MODE) { + $shuffle = static function (array $array) { + \shuffle($array); + + return $array; + }; + } else { + $shuffle = static function (array $array) { + return $array; + }; + } + + return [ + EOT; + $result .= "\n"; + + foreach ($groups as $group) { + $group = \array_values(\array_filter($group, $this->bootstrapExists(...))); + + if ($group === []) { + continue; + } + + if (\count($group) === 1) { + $package = $group[0]; + $result .= " require(__DIR__ . '/{$this->getRelativeBootstrapFilename($package)}'),\n"; + } else { + $result .= " ...\$shuffle([\n"; + \shuffle($group); + foreach ($group as $package) { + $result .= " require(__DIR__ . '/{$this->getRelativeBootstrapFilename($package)}'),\n"; + } + $result .= " ]),\n"; + } + } + + $result .= <<<'EOT' + ]; + })(); + EOT; + $result .= "\n"; + + $writer = new AtomicWriter(WCF::BOOTSTRAP_LOADER); + $writer->write($result); + $writer->flush(); + + WCF::resetZendOpcache(WCF::BOOTSTRAP_LOADER); + } + + private function bootstrapExists(Package $package): bool + { + return \file_exists($this->getBootstrapFilename($package)); + } + + private function getBootstrapFilename(Package $package): string + { + return \WCF_DIR . 'lib/' . $this->getRelativeBootstrapFilename($package); + } + + private function getRelativeBootstrapFilename(Package $package): string + { + return 'bootstrap/' . $package->package . '.php'; + } +} diff --git a/wcfsetup/install/files/lib/command/style/AddDarkMode.class.php b/wcfsetup/install/files/lib/command/style/AddDarkMode.class.php new file mode 100644 index 00000000000..906518e90ae --- /dev/null +++ b/wcfsetup/install/files/lib/command/style/AddDarkMode.class.php @@ -0,0 +1,36 @@ + + * @since 6.2 + */ +final class AddDarkMode +{ + public function __construct( + private readonly Style $style + ) {} + + public function __invoke(): void + { + $styleEditor = new StyleEditor($this->style); + $styleEditor->update([ + 'hasDarkMode' => 1, + ]); + + $style = new Style($this->style->styleID); + + StyleCacheBuilder::getInstance()->reset(); + StyleHandler::getInstance()->resetStylesheet($style); + } +} diff --git a/wcfsetup/install/files/lib/system/style/command/CopyStyle.class.php b/wcfsetup/install/files/lib/command/style/CopyStyle.class.php similarity index 99% rename from wcfsetup/install/files/lib/system/style/command/CopyStyle.class.php rename to wcfsetup/install/files/lib/command/style/CopyStyle.class.php index 533da2bd97e..19db90bea86 100644 --- a/wcfsetup/install/files/lib/system/style/command/CopyStyle.class.php +++ b/wcfsetup/install/files/lib/command/style/CopyStyle.class.php @@ -1,6 +1,6 @@ + * @since 6.2 + */ +final class CreateManifest +{ + public function __construct( + private readonly Style $style + ) {} + + public function __invoke(): void + { + $this->style->loadVariables(); + $headerColor = $this->style->getVariable('wcfHeaderBackground', true); + $backgroundColor = $this->style->getVariable('wcfContentBackground', true); + $landingPage = PageCache::getInstance()->getLandingPage(); + + $icons = []; + foreach ([192, 256, 512] as $iconSize) { + $icons[] = [ + "src" => \sprintf( + "%sandroid-chrome-%dx%d.png", + $this->style->hasFavicon ? "" : "../favicon/default.", + $iconSize, + $iconSize + ), + "sizes" => "{$iconSize}x{$iconSize}", + "type" => "image/png" + ]; + } + $icons = JSON::encode($icons); + + $originalLanguage = WCF::getLanguage(); + try { + foreach (LanguageFactory::getInstance()->getLanguages() as $language) { + // To get the correct landing page url, we need to change the language. + WCF::setLanguage($language->languageID); + + $title = JSON::encode($language->get(PAGE_TITLE)); + $startUrl = JSON::encode($landingPage->getLink()); + + // update manifest.json + $manifest = <<style->getAssetPath() . "manifest-{$language->languageID}.json"; + if (\file_exists($manifestPath) && \hash_equals(\sha1_file($manifestPath), \sha1($manifest))) { + continue; + } + $writer = new AtomicWriter($manifestPath); + $writer->write($manifest); + $writer->flush(); + $writer->close(); + } + } finally { + WCF::setLanguage($originalLanguage->languageID); + } + } +} diff --git a/wcfsetup/install/files/lib/system/tagging/command/SetSynonym.class.php b/wcfsetup/install/files/lib/command/tag/SetTagSynonym.class.php similarity index 93% rename from wcfsetup/install/files/lib/system/tagging/command/SetSynonym.class.php rename to wcfsetup/install/files/lib/command/tag/SetTagSynonym.class.php index 9a0b63dd594..386d01f0759 100644 --- a/wcfsetup/install/files/lib/system/tagging/command/SetSynonym.class.php +++ b/wcfsetup/install/files/lib/command/tag/SetTagSynonym.class.php @@ -1,6 +1,6 @@ * @since 6.2 */ -final class SetSynonym +final class SetTagSynonym { /** * @param Tag[] $tags diff --git a/wcfsetup/install/files/lib/command/user/CreateRegistrationNotification.class.php b/wcfsetup/install/files/lib/command/user/CreateRegistrationNotification.class.php new file mode 100644 index 00000000000..798f7e93e1c --- /dev/null +++ b/wcfsetup/install/files/lib/command/user/CreateRegistrationNotification.class.php @@ -0,0 +1,66 @@ + + * @since 6.2 + */ +final class CreateRegistrationNotification +{ + public function __construct( + private readonly User $user + ) {} + + public function __invoke(): void + { + if ($this->user->requiresEmailActivation()) { + return; + } + + $recipientIDs = $this->getRecipientsForNotificationEvent(); + if (!empty($recipientIDs)) { + UserNotificationHandler::getInstance()->fireEvent( + $this->user->requiresAdminActivation() ? 'needActivation' : 'success', + 'com.woltlab.wcf.user.registration.notification', + new UserRegistrationUserNotificationObject($this->user), + $recipientIDs + ); + } + } + + /** + * @return int[] + */ + private function getRecipientsForNotificationEvent(): array + { + $sql = "SELECT userID + FROM wcf1_user_to_group + WHERE groupID IN ( + SELECT groupID + FROM wcf1_user_group_option_value + WHERE optionID IN ( + SELECT optionID + FROM wcf1_user_group_option + WHERE optionName = ? + ) + AND optionValue = ? + )"; + $statement = WCF::getDB()->prepare($sql, 100); + $statement->execute([ + 'admin.user.canSearchUser', + 1, + ]); + + return $statement->fetchAll(\PDO::FETCH_COLUMN); + } +} diff --git a/wcfsetup/install/files/lib/command/user/Follow.class.php b/wcfsetup/install/files/lib/command/user/Follow.class.php new file mode 100644 index 00000000000..1a4b0d81dba --- /dev/null +++ b/wcfsetup/install/files/lib/command/user/Follow.class.php @@ -0,0 +1,68 @@ + + * @since 6.2 + */ +final class Follow +{ + public function __construct( + private readonly User $user, + private readonly User $target + ) {} + + public function __invoke(): void + { + $follow = UserFollowEditor::createOrIgnore([ + 'userID' => $this->user->userID, + 'followUserID' => $this->target->userID, + 'time' => TIME_NOW, + ]); + + if ($follow === null) { + return; + } + + $this->sendNotification($follow); + $this->fireActivityEvent(); + $this->resetUserStorage(); + } + + private function sendNotification(UserFollow $follow): void + { + UserNotificationHandler::getInstance()->fireEvent( + 'following', + 'com.woltlab.wcf.user.follow', + new UserFollowUserNotificationObject($follow), + [$follow->followUserID] + ); + } + + private function fireActivityEvent(): void + { + UserActivityEventHandler::getInstance()->fireEvent( + 'com.woltlab.wcf.user.recentActivityEvent.follow', + $this->target->userID + ); + } + + private function resetUserStorage(): void + { + UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs'); + UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs'); + } +} diff --git a/wcfsetup/install/files/lib/system/user/command/SetAvatar.class.php b/wcfsetup/install/files/lib/command/user/SetAvatar.class.php similarity index 98% rename from wcfsetup/install/files/lib/system/user/command/SetAvatar.class.php rename to wcfsetup/install/files/lib/command/user/SetAvatar.class.php index db3fa052458..27417ab91ef 100644 --- a/wcfsetup/install/files/lib/system/user/command/SetAvatar.class.php +++ b/wcfsetup/install/files/lib/command/user/SetAvatar.class.php @@ -1,6 +1,6 @@ + * @since 6.2 + */ +final class SetColorScheme +{ + public function __construct( + private readonly User $user, + private readonly string $colorScheme + ) {} + + public function __invoke(): void + { + $userAction = new UserAction([$this->user], 'update', [ + 'options' => [ + User::getUserOptionID('colorScheme') => $this->colorScheme, + ], + ]); + $userAction->executeAction(); + } +} diff --git a/wcfsetup/install/files/lib/system/user/command/SetCoverPhoto.class.php b/wcfsetup/install/files/lib/command/user/SetCoverPhoto.class.php similarity index 95% rename from wcfsetup/install/files/lib/system/user/command/SetCoverPhoto.class.php rename to wcfsetup/install/files/lib/command/user/SetCoverPhoto.class.php index 2427ea100c9..e2fea08879b 100644 --- a/wcfsetup/install/files/lib/system/user/command/SetCoverPhoto.class.php +++ b/wcfsetup/install/files/lib/command/user/SetCoverPhoto.class.php @@ -1,6 +1,6 @@ + * @since 6.2 + */ +final class Unfollow +{ + public function __construct( + private readonly User $user, + private readonly User $target + ) {} + + public function __invoke(): void + { + $follow = UserFollow::getFollow($this->user->userID, $this->target->userID); + + if ($follow->followID) { + $followEditor = new UserFollowEditor($follow); + $followEditor->delete(); + + $this->removeActivityEvent(); + } + + $this->resetUserStorage(); + } + + private function removeActivityEvent(): void + { + UserActivityEventHandler::getInstance()->removeEvent( + 'com.woltlab.wcf.user.recentActivityEvent.follow', + $this->target->userID + ); + } + + private function resetUserStorage(): void + { + UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs'); + UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs'); + } +} diff --git a/wcfsetup/install/files/lib/system/user/group/command/CopyUserGroup.class.php b/wcfsetup/install/files/lib/command/user/group/CopyUserGroup.class.php similarity index 99% rename from wcfsetup/install/files/lib/system/user/group/command/CopyUserGroup.class.php rename to wcfsetup/install/files/lib/command/user/group/CopyUserGroup.class.php index b8ab40657dd..fc527f5caec 100644 --- a/wcfsetup/install/files/lib/system/user/group/command/CopyUserGroup.class.php +++ b/wcfsetup/install/files/lib/command/user/group/CopyUserGroup.class.php @@ -1,6 +1,6 @@ + * @since 6.2 */ final class CopyUserGroup { diff --git a/wcfsetup/install/files/lib/system/user/option/command/DeleteOption.class.php b/wcfsetup/install/files/lib/command/user/option/DeleteOption.class.php similarity index 93% rename from wcfsetup/install/files/lib/system/user/option/command/DeleteOption.class.php rename to wcfsetup/install/files/lib/command/user/option/DeleteOption.class.php index 8106231f8e4..4ba83f75a6e 100644 --- a/wcfsetup/install/files/lib/system/user/option/command/DeleteOption.class.php +++ b/wcfsetup/install/files/lib/command/user/option/DeleteOption.class.php @@ -1,6 +1,6 @@ user->userID)); + $command = new \wcf\command\user\CreateRegistrationNotification(new User($this->user->userID)); $command(); HeaderUtil::delayedRedirect(LinkHandler::getInstance()->getLink(), $redirectText, 10, 'success', true); diff --git a/wcfsetup/install/files/lib/form/RegisterForm.class.php b/wcfsetup/install/files/lib/form/RegisterForm.class.php index 17598a3a495..fc8955d22cd 100644 --- a/wcfsetup/install/files/lib/form/RegisterForm.class.php +++ b/wcfsetup/install/files/lib/form/RegisterForm.class.php @@ -21,11 +21,10 @@ use wcf\system\exception\PermissionDeniedException; use wcf\system\exception\SystemException; use wcf\system\exception\UserInputException; -use wcf\system\option\user\UserOptionHandler; use wcf\system\request\LinkHandler; use wcf\system\user\authentication\configuration\UserAuthenticationConfigurationFactory; use wcf\system\user\authentication\LoginRedirect; -use wcf\system\user\command\CreateRegistrationNotification; +use wcf\command\user\CreateRegistrationNotification; use wcf\system\user\group\assignment\UserGroupAssignmentHandler; use wcf\system\WCF; use wcf\util\HeaderUtil; diff --git a/wcfsetup/install/files/lib/form/SettingsForm.class.php b/wcfsetup/install/files/lib/form/SettingsForm.class.php index 7e08aa2c5ec..d86c53b9c96 100644 --- a/wcfsetup/install/files/lib/form/SettingsForm.class.php +++ b/wcfsetup/install/files/lib/form/SettingsForm.class.php @@ -18,7 +18,7 @@ use wcf\system\option\user\UserOptionHandler; use wcf\system\request\LinkHandler; use wcf\system\style\StyleHandler; -use wcf\system\user\command\SetColorScheme; +use wcf\command\user\SetColorScheme; use wcf\system\user\storage\UserStorageHandler; use wcf\system\WCF; use wcf\util\ArrayUtil; diff --git a/wcfsetup/install/files/lib/system/WCF.class.php b/wcfsetup/install/files/lib/system/WCF.class.php index 0c9db1db37e..65d0b138e10 100644 --- a/wcfsetup/install/files/lib/system/WCF.class.php +++ b/wcfsetup/install/files/lib/system/WCF.class.php @@ -20,7 +20,6 @@ use wcf\system\exception\IPrintableException; use wcf\system\exception\SystemException; use wcf\system\language\LanguageFactory; -use wcf\system\package\command\RebuildBootstrapper; use wcf\system\package\PackageInstallationDispatcher; use wcf\system\registry\RegistryHandler; use wcf\system\request\Request; @@ -218,7 +217,7 @@ final protected function runBootstrappers(): void try { $bootstrappers = require(self::BOOTSTRAP_LOADER); } catch (\Exception $e) { - $command = new RebuildBootstrapper(); + $command = new \wcf\command\package\RebuildBootstrapper(); $command(); $bootstrappers = require(self::BOOTSTRAP_LOADER); diff --git a/wcfsetup/install/files/lib/system/acp/dashboard/command/ConfigureBoxes.class.php b/wcfsetup/install/files/lib/system/acp/dashboard/command/ConfigureBoxes.class.php index f2737c73e81..77bd61051f5 100644 --- a/wcfsetup/install/files/lib/system/acp/dashboard/command/ConfigureBoxes.class.php +++ b/wcfsetup/install/files/lib/system/acp/dashboard/command/ConfigureBoxes.class.php @@ -13,6 +13,7 @@ * @copyright 2001-2023 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\acp\dashboard\ConfigureBoxes` instead. */ final class ConfigureBoxes { @@ -23,47 +24,10 @@ public function __construct( private readonly AcpDashboard $dashboard, private readonly User $user, private readonly array $boxes, - ) { - } + ) {} public function __invoke(): void { - $this->resetBoxes(); - $this->saveBoxes(); - } - - private function resetBoxes(): void - { - $sql = "DELETE FROM wcf1_acp_dashboard_box_to_user WHERE userID = ?"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([$this->user->userID]); - } - - private function saveBoxes(): void - { - $sql = "INSERT INTO wcf1_acp_dashboard_box_to_user (boxName, userID, enabled, showOrder) VALUES (?, ?, ?, ?)"; - $statement = WCF::getDB()->prepare($sql); - $showOrder = 0; - foreach ($this->boxes as $boxName) { - $statement->execute([ - $boxName, - $this->user->userID, - 1, - $showOrder++ - ]); - } - - foreach ($this->dashboard->getBoxes() as $box) { - if (\in_array($box->getName(), $this->boxes)) { - continue; - } - - $statement->execute([ - $box->getName(), - $this->user->userID, - 0, - 0 - ]); - } + (new \wcf\command\acp\dashboard\ConfigureBoxes($this->dashboard, $this->user, $this->boxes))(); } } diff --git a/wcfsetup/install/files/lib/system/box/BoxHandler.class.php b/wcfsetup/install/files/lib/system/box/BoxHandler.class.php index d53e1c3c0b6..c4c5a87a40b 100644 --- a/wcfsetup/install/files/lib/system/box/BoxHandler.class.php +++ b/wcfsetup/install/files/lib/system/box/BoxHandler.class.php @@ -4,8 +4,8 @@ use wcf\data\box\Box; use wcf\data\box\BoxList; -use wcf\system\box\command\CreateBoxCondition; -use wcf\system\box\command\CreateBoxToPageAssignments; +use wcf\command\box\CreateBoxCondition; +use wcf\command\box\CreateBoxToPageAssignments; use wcf\system\event\EventHandler; use wcf\system\request\RequestHandler; use wcf\system\SingletonFactory; diff --git a/wcfsetup/install/files/lib/system/box/command/CreateBoxCondition.class.php b/wcfsetup/install/files/lib/system/box/command/CreateBoxCondition.class.php index dcc0f3a1cec..8d4f1696550 100644 --- a/wcfsetup/install/files/lib/system/box/command/CreateBoxCondition.class.php +++ b/wcfsetup/install/files/lib/system/box/command/CreateBoxCondition.class.php @@ -2,8 +2,6 @@ namespace wcf\system\box\command; -use wcf\data\box\Box; -use wcf\data\condition\ConditionAction; use wcf\system\WCF; /** @@ -15,6 +13,7 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\box\CreateBoxCondition` instead. */ final class CreateBoxCondition { @@ -26,47 +25,18 @@ public function __construct( private readonly string $conditionDefinition, private readonly string $conditionObjectType, private readonly array $conditionData - ) { - } + ) {} /** * @throws \InvalidArgumentException */ public function __invoke(): void { - $objectTypeID = $this->getObjectTypeID(); - if (!$objectTypeID) { - throw new \InvalidArgumentException( - "Unknown box condition '{$this->conditionObjectType}' of condition definition '{$this->conditionDefinition}'" - ); - } - - $box = Box::getBoxByIdentifier($this->boxIdentifier); - if ($box === null) { - throw new \InvalidArgumentException("Unknown box with identifier '{$this->boxIdentifier}'"); - } - - (new ConditionAction([], 'create', [ - 'data' => [ - 'conditionData' => \serialize($this->conditionData), - 'objectID' => $box->boxID, - 'objectTypeID' => $objectTypeID, - ], - ]))->executeAction(); - } - - private function getObjectTypeID(): ?int - { - // do not rely on caches during package installation - $sql = "SELECT objectTypeID - FROM wcf1_object_type object_type - INNER JOIN wcf1_object_type_definition object_type_definition - ON object_type.definitionID = object_type_definition.definitionID - WHERE objectType = ? - AND definitionName = ?"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([$this->conditionObjectType, $this->conditionDefinition]); - - return $statement->fetchSingleColumn(); + (new \wcf\command\box\CreateBoxCondition( + $this->boxIdentifier, + $this->conditionDefinition, + $this->conditionObjectType, + $this->conditionData + ))(); } } diff --git a/wcfsetup/install/files/lib/system/box/command/CreateBoxToPageAssignments.class.php b/wcfsetup/install/files/lib/system/box/command/CreateBoxToPageAssignments.class.php index c78be25db7a..b2c156a87ed 100644 --- a/wcfsetup/install/files/lib/system/box/command/CreateBoxToPageAssignments.class.php +++ b/wcfsetup/install/files/lib/system/box/command/CreateBoxToPageAssignments.class.php @@ -2,8 +2,6 @@ namespace wcf\system\box\command; -use wcf\data\box\Box; -use wcf\data\page\Page; use wcf\system\WCF; /** @@ -15,6 +13,7 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\box\CreateBoxToPageAssignments` instead. */ final class CreateBoxToPageAssignments { @@ -25,44 +24,17 @@ public function __construct( private readonly string $boxIdentifier, private readonly array $pageIdentifiers, private readonly bool $visible = true, - ) { - } + ) {} /** * @throws \InvalidArgumentException */ public function __invoke(): void { - $box = Box::getBoxByIdentifier($this->boxIdentifier); - if ($box === null) { - throw new \InvalidArgumentException("Unknown box with identifier '{$this->boxIdentifier}'"); - } - - $pages = []; - foreach ($this->pageIdentifiers as $pageIdentifier) { - $page = Page::getPageByIdentifier($pageIdentifier); - if ($page === null) { - throw new \InvalidArgumentException("Unknown page with identifier '{$pageIdentifier}'"); - } - $pages[] = $page; - } - - if (($this->visible && $box->visibleEverywhere) || (!$this->visible && !$box->visibleEverywhere)) { - $sql = "DELETE FROM wcf1_box_to_page - WHERE boxID = ? - AND pageID = ?"; - $statement = WCF::getDB()->prepare($sql); - foreach ($pages as $page) { - $statement->execute([$box->boxID, $page->pageID]); - } - } else { - $sql = "REPLACE INTO wcf1_box_to_page - (boxID, pageID, visible) - VALUES (?, ?, ?)"; - $statement = WCF::getDB()->prepare($sql); - foreach ($pages as $page) { - $statement->execute([$box->boxID, $page->pageID, $this->visible ? 1 : 0]); - } - } + (new \wcf\command\box\CreateBoxToPageAssignments( + $this->boxIdentifier, + $this->pageIdentifiers, + $this->visible + ))(); } } diff --git a/wcfsetup/install/files/lib/system/cache/command/ClearCache.class.php b/wcfsetup/install/files/lib/system/cache/command/ClearCache.class.php index 50f31330af6..685bff50edf 100644 --- a/wcfsetup/install/files/lib/system/cache/command/ClearCache.class.php +++ b/wcfsetup/install/files/lib/system/cache/command/ClearCache.class.php @@ -2,48 +2,19 @@ namespace wcf\system\cache\command; -use wcf\data\option\OptionEditor; -use wcf\data\package\update\server\PackageUpdateServer; -use wcf\system\cache\CacheHandler; -use wcf\system\cache\event\CacheCleared; -use wcf\system\event\EventHandler; -use wcf\system\language\LanguageFactory; -use wcf\system\style\StyleHandler; -use wcf\system\user\storage\UserStorageHandler; - /** * Performs a full cache clear. * - * @author Tim Duesterhus + * @author Tim Duesterhus * @copyright 2001-2021 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\cache\ClearCache` instead. */ final class ClearCache { - private EventHandler $eventHandler; - - public function __construct() - { - $this->eventHandler = EventHandler::getInstance(); - } - public function __invoke(): void { - OptionEditor::resetCache(); - - UserStorageHandler::getInstance()->clear(); - - StyleHandler::resetStylesheets(); - - LanguageFactory::getInstance()->deleteLanguageCache(); - - CacheHandler::getInstance()->flushAll(); - - PackageUpdateServer::resetAll(); - - $this->eventHandler->fire( - new CacheCleared() - ); + (new \wcf\command\cache\ClearCache())(); } } diff --git a/wcfsetup/install/files/lib/system/comment/command/CreateComment.class.php b/wcfsetup/install/files/lib/system/comment/command/CreateComment.class.php index a9f450c3308..14a06a6a88e 100644 --- a/wcfsetup/install/files/lib/system/comment/command/CreateComment.class.php +++ b/wcfsetup/install/files/lib/system/comment/command/CreateComment.class.php @@ -3,15 +3,9 @@ namespace wcf\system\comment\command; use wcf\data\comment\Comment; -use wcf\data\comment\CommentAction; -use wcf\data\comment\CommentEditor; use wcf\data\object\type\ObjectType; use wcf\data\user\User; -use wcf\event\comment\CommentCreated; -use wcf\system\event\EventHandler; use wcf\system\html\input\HtmlInputProcessor; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; -use wcf\system\moderation\queue\ModerationQueueActivationManager; /** * Creates a new comment. @@ -20,6 +14,7 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\CreateComment` instead. */ final class CreateComment { @@ -30,48 +25,17 @@ public function __construct( private readonly ?User $user = null, private readonly string $username = '', private readonly bool $isDisabled = false, - ) { - } + ) {} public function __invoke(): Comment { - $action = new CommentAction([], 'create', [ - 'data' => [ - 'objectTypeID' => $this->objectType->objectTypeID, - 'objectID' => $this->objectID, - 'time' => TIME_NOW, - 'userID' => $this->user ? $this->user->userID : null, - 'username' => $this->user ? $this->user->username : $this->username, - 'message' => $this->htmlInputProcessor->getHtml(), - 'responses' => 0, - 'responseIDs' => \serialize([]), - 'enableHtml' => 1, - 'isDisabled' => $this->isDisabled ? 1 : 0, - ] - ]); - $comment = $action->executeAction()['returnValues']; - \assert($comment instanceof Comment); - - $this->htmlInputProcessor->setObjectID($comment->getObjectID()); - if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor)) { - (new CommentEditor($comment))->update([ - 'hasEmbeddedObjects' => 1, - ]); - $comment = new Comment($comment->getObjectID()); - } - - if (!$comment->isDisabled) { - (new PublishComment($comment))(); - } else { - ModerationQueueActivationManager::getInstance()->addModeratedContent( - 'com.woltlab.wcf.comment.comment', - $comment->commentID - ); - } - - $event = new CommentCreated($comment); - EventHandler::getInstance()->fire($event); - - return $comment; + return (new \wcf\command\comment\CreateComment( + $this->objectType, + $this->objectID, + $this->htmlInputProcessor, + $this->user, + $this->username, + $this->isDisabled + ))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/command/DeleteComments.class.php b/wcfsetup/install/files/lib/system/comment/command/DeleteComments.class.php index fd90b56b087..3ba6c99f251 100644 --- a/wcfsetup/install/files/lib/system/comment/command/DeleteComments.class.php +++ b/wcfsetup/install/files/lib/system/comment/command/DeleteComments.class.php @@ -3,19 +3,6 @@ namespace wcf\system\comment\command; use wcf\data\comment\Comment; -use wcf\data\comment\CommentAction; -use wcf\data\comment\response\CommentResponseList; -use wcf\data\object\type\ObjectType; -use wcf\event\comment\CommentsDeleted; -use wcf\system\comment\CommentHandler; -use wcf\system\comment\manager\ICommentManager; -use wcf\system\comment\response\command\DeleteResponses; -use wcf\system\event\EventHandler; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; -use wcf\system\moderation\queue\ModerationQueueManager; -use wcf\system\reaction\ReactionHandler; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\notification\UserNotificationHandler; /** * Deletes a bunch of comments that belong to the same object type. @@ -24,118 +11,20 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\DeleteComments` instead. */ final class DeleteComments { - private readonly ObjectType $objectType; - private readonly ICommentManager $commentManager; - /** - * @var int[] - */ - private readonly array $commentIDs; - /** * @param Comment[] $comments */ public function __construct( private readonly array $comments, private readonly bool $updateCounters = true, - ) { - $this->commentIDs = \array_column($this->comments, 'commentID'); - $firstCommentID = \array_key_first($this->comments); - \assert($firstCommentID !== null); - $firstComment = $this->comments[$firstCommentID]; - $this->objectType = CommentHandler::getInstance()->getObjectType($firstComment->objectTypeID); - $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($firstComment->objectTypeID); - } + ) {} public function __invoke(): void { - $this->deleteActivityEvents(); - $this->deleteNotifications(); - $this->deleteReactions(); - $this->deleteModerationQueues(); - $this->deleteMessageEmbeddedObjects(); - $this->deleteResponses(); - - $action = new CommentAction($this->commentIDs, 'delete'); - $action->executeAction(); - - $this->updateCounters(); - - $event = new CommentsDeleted($this->comments); - EventHandler::getInstance()->fire($event); - } - - private function deleteActivityEvents(): void - { - if (UserActivityEventHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.recentActivityEvent')) { - UserActivityEventHandler::getInstance()->removeEvents( - $this->objectType->objectType . '.recentActivityEvent', - $this->commentIDs - ); - } - } - - private function deleteNotifications(): void - { - if (UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.notification')) { - UserNotificationHandler::getInstance()->removeNotifications( - $this->objectType->objectType . '.notification', - $this->commentIDs - ); - } - } - - private function deleteReactions(): void - { - ReactionHandler::getInstance()->removeReactions( - 'com.woltlab.wcf.comment', - $this->commentIDs, - UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.like.notification') - ? [$this->objectType->objectType . '.like.notification'] - : [] - ); - } - - private function deleteResponses(): void - { - $responseList = new CommentResponseList(); - $responseList->getConditionBuilder()->add('comment_response.commentID IN (?)', [$this->commentIDs]); - $responseList->readObjects(); - if (!\count($responseList)) { - return; - } - - (new DeleteResponses($responseList->getObjects(), $this->updateCounters))(); - } - - private function deleteModerationQueues(): void - { - ModerationQueueManager::getInstance()->removeQueues( - 'com.woltlab.wcf.comment.comment', - $this->commentIDs - ); - } - - private function deleteMessageEmbeddedObjects(): void - { - MessageEmbeddedObjectManager::getInstance()->removeObjects( - 'com.woltlab.wcf.comment', - $this->commentIDs - ); - } - - private function updateCounters(): void - { - if (!$this->updateCounters) { - return; - } - - foreach ($this->comments as $comment) { - if (!$comment->isDisabled) { - $this->commentManager->updateCounter($comment->objectID, -1); - } - } + (new \wcf\command\comment\DeleteComments($this->comments, $this->updateCounters))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/command/PublishComment.class.php b/wcfsetup/install/files/lib/system/comment/command/PublishComment.class.php index 0b1b856162f..4698eee51db 100644 --- a/wcfsetup/install/files/lib/system/comment/command/PublishComment.class.php +++ b/wcfsetup/install/files/lib/system/comment/command/PublishComment.class.php @@ -3,17 +3,6 @@ namespace wcf\system\comment\command; use wcf\data\comment\Comment; -use wcf\data\comment\CommentEditor; -use wcf\data\object\type\ObjectType; -use wcf\event\comment\CommentPublished; -use wcf\system\comment\CommentHandler; -use wcf\system\comment\manager\ICommentManager; -use wcf\system\event\EventHandler; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\notification\object\CommentUserNotificationObject; -use wcf\system\user\notification\object\type\ICommentUserNotificationObjectType; -use wcf\system\user\notification\object\type\IMultiRecipientCommentUserNotificationObjectType; -use wcf\system\user\notification\UserNotificationHandler; /** * Publishes a comment. @@ -22,82 +11,16 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\PublishComment` instead. */ final class PublishComment { - private readonly ObjectType $objectType; - private readonly ICommentManager $commentManager; - public function __construct( private readonly Comment $comment, - ) { - $this->objectType = CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID); - $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($this->comment->objectTypeID); - } + ) {} public function __invoke(): void { - if ($this->comment->isDisabled) { - (new CommentEditor($this->comment))->update([ - 'isDisabled' => 0 - ]); - } - $this->commentManager->updateCounter($this->comment->objectID, 1); - - $this->fireActivityEvent(); - $this->fireNotificationEvent(); - - $event = new CommentPublished($this->comment); - EventHandler::getInstance()->fire($event); - } - - private function fireActivityEvent(): void - { - if ( - $this->comment->userID - && UserActivityEventHandler::getInstance()->getObjectTypeID( - $this->objectType->objectType . '.recentActivityEvent' - ) - ) { - UserActivityEventHandler::getInstance()->fireEvent( - $this->objectType->objectType . '.recentActivityEvent', - $this->comment->commentID, - null, - $this->comment->userID, - $this->comment->time - ); - } - } - - private function fireNotificationEvent(): void - { - if (!UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.notification', 'comment')) { - return; - } - - $notificationObject = new CommentUserNotificationObject($this->comment); - $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor( - $this->objectType->objectType . '.notification' - ); - - if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) { - $recipientIDs = $notificationObjectType->getRecipientIDs($this->comment); - } else { - $recipientIDs = []; - } - - if ($notificationObjectType instanceof ICommentUserNotificationObjectType) { - $recipientIDs[] = $notificationObjectType->getOwnerID($this->comment->commentID); - } - - // make sure that the comment's author gets no notification - $recipientIDs = \array_diff($recipientIDs, [$this->comment->getUserID()]); - - UserNotificationHandler::getInstance()->fireEvent( - 'comment', - $this->objectType->objectType . '.notification', - $notificationObject, - $recipientIDs - ); + (new \wcf\command\comment\PublishComment($this->comment))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/command/UpdateComment.class.php b/wcfsetup/install/files/lib/system/comment/command/UpdateComment.class.php index a3bfa437875..b984a8d6990 100644 --- a/wcfsetup/install/files/lib/system/comment/command/UpdateComment.class.php +++ b/wcfsetup/install/files/lib/system/comment/command/UpdateComment.class.php @@ -3,11 +3,7 @@ namespace wcf\system\comment\command; use wcf\data\comment\Comment; -use wcf\data\comment\CommentAction; -use wcf\event\comment\CommentUpdated; -use wcf\system\event\EventHandler; use wcf\system\html\input\HtmlInputProcessor; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; /** * Updates a comment. @@ -16,33 +12,17 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\UpdateComment` instead. */ final class UpdateComment { public function __construct( private readonly Comment $comment, private readonly HtmlInputProcessor $htmlInputProcessor, - ) { - } + ) {} public function __invoke(): void { - $data = [ - 'message' => $this->htmlInputProcessor->getHtml(), - ]; - - $this->htmlInputProcessor->setObjectID($this->comment->getObjectID()); - $hasEmbeddedObjects = MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor); - if ($this->comment->hasEmbeddedObjects != $hasEmbeddedObjects) { - $data['hasEmbeddedObjects'] = $this->comment->hasEmbeddedObjects ? 0 : 1; - } - - $action = new CommentAction([$this->comment], 'update', [ - 'data' => $data, - ]); - $action->executeAction(); - - $event = new CommentUpdated(new Comment($this->comment->commentID)); - EventHandler::getInstance()->fire($event); + (new \wcf\command\comment\UpdateComment($this->comment, $this->htmlInputProcessor))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/response/command/CreateResponse.class.php b/wcfsetup/install/files/lib/system/comment/response/command/CreateResponse.class.php index 4ac427abacf..d9036aa7f8f 100644 --- a/wcfsetup/install/files/lib/system/comment/response/command/CreateResponse.class.php +++ b/wcfsetup/install/files/lib/system/comment/response/command/CreateResponse.class.php @@ -3,16 +3,9 @@ namespace wcf\system\comment\response\command; use wcf\data\comment\Comment; -use wcf\data\comment\CommentEditor; use wcf\data\comment\response\CommentResponse; -use wcf\data\comment\response\CommentResponseAction; -use wcf\data\comment\response\CommentResponseEditor; use wcf\data\user\User; -use wcf\event\comment\response\ResponseCreated; -use wcf\system\event\EventHandler; use wcf\system\html\input\HtmlInputProcessor; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; -use wcf\system\moderation\queue\ModerationQueueActivationManager; /** * Creates a new comment response. @@ -21,6 +14,7 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\response\CreateResponse` instead. */ final class CreateResponse { @@ -30,61 +24,16 @@ public function __construct( private readonly ?User $user = null, private readonly string $username = '', private readonly bool $isDisabled = false, - ) { - } + ) {} public function __invoke(): CommentResponse { - $action = new CommentResponseAction([], 'create', [ - 'data' => [ - 'commentID' => $this->comment->commentID, - 'time' => TIME_NOW, - 'userID' => $this->user ? $this->user->userID : null, - 'username' => $this->user ? $this->user->username : $this->username, - 'message' => $this->htmlInputProcessor->getHtml(), - 'enableHtml' => 1, - 'isDisabled' => $this->isDisabled ? 1 : 0, - ] - ]); - $response = $action->executeAction()['returnValues']; - \assert($response instanceof CommentResponse); - - $this->updateResponseData($response); - - $this->htmlInputProcessor->setObjectID($response->getObjectID()); - if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor)) { - (new CommentResponseEditor($response))->update([ - 'hasEmbeddedObjects' => 1, - ]); - $response = new CommentResponse($response->getObjectID()); - } - - if (!$response->isDisabled) { - (new PublishResponse($response))(); - } else { - ModerationQueueActivationManager::getInstance()->addModeratedContent( - 'com.woltlab.wcf.comment.response', - $response->responseID - ); - } - - $event = new ResponseCreated($response); - EventHandler::getInstance()->fire($event); - - return $response; - } - - private function updateResponseData(CommentResponse $response): void - { - $unfilteredResponseIDs = $this->comment->getUnfilteredResponseIDs(); - if (\count($unfilteredResponseIDs) < 5) { - $unfilteredResponseIDs[] = $response->responseID; - } - $unfilteredResponses = $this->comment->unfilteredResponses + 1; - - (new CommentEditor($this->comment))->update([ - 'unfilteredResponseIDs' => \serialize($unfilteredResponseIDs), - 'unfilteredResponses' => $unfilteredResponses, - ]); + return (new \wcf\command\comment\response\CreateResponse( + $this->comment, + $this->htmlInputProcessor, + $this->user, + $this->username, + $this->isDisabled + ))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/response/command/DeleteResponses.class.php b/wcfsetup/install/files/lib/system/comment/response/command/DeleteResponses.class.php index be4aee65637..30cebfaffb8 100644 --- a/wcfsetup/install/files/lib/system/comment/response/command/DeleteResponses.class.php +++ b/wcfsetup/install/files/lib/system/comment/response/command/DeleteResponses.class.php @@ -2,21 +2,7 @@ namespace wcf\system\comment\response\command; -use wcf\data\comment\Comment; -use wcf\data\comment\CommentEditor; -use wcf\data\comment\CommentList; use wcf\data\comment\response\CommentResponse; -use wcf\data\comment\response\CommentResponseAction; -use wcf\data\object\type\ObjectType; -use wcf\event\comment\response\ResponsesDeleted; -use wcf\system\comment\CommentHandler; -use wcf\system\comment\manager\ICommentManager; -use wcf\system\event\EventHandler; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; -use wcf\system\moderation\queue\ModerationQueueManager; -use wcf\system\reaction\ReactionHandler; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\notification\UserNotificationHandler; /** * Deletes a bunch of comment responses that belong to the same object type. @@ -25,125 +11,23 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\response\DeleteResponses` instead. */ final class DeleteResponses { - private readonly ObjectType $objectType; - private readonly ICommentManager $commentManager; - - /** - * @var int[] - */ - private readonly array $responseIDs; - /** * @param CommentResponse[] $responses */ public function __construct( private readonly array $responses, private readonly bool $updateCounters = true, - ) { - $this->responseIDs = \array_column($this->responses, 'responseID'); - $firstResponseKey = \array_key_first($this->responses); - \assert($firstResponseKey !== null); - $firstResponse = $this->responses[$firstResponseKey]; - $this->objectType = CommentHandler::getInstance()->getObjectType($firstResponse->getComment()->objectTypeID); - $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($firstResponse->getComment()->objectTypeID); - } + ) {} public function __invoke(): void { - $this->deleteActivityEvents(); - $this->deleteNotifications(); - $this->deleteReactions(); - $this->deleteModerationQueues(); - $this->deleteMessageEmbeddedObjects(); - - $action = new CommentResponseAction($this->responseIDs, 'delete'); - $action->executeAction(); - - $this->updateCounters(); - - $event = new ResponsesDeleted($this->responses); - EventHandler::getInstance()->fire($event); - } - - private function deleteActivityEvents(): void - { - if (UserActivityEventHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.recentActivityEvent')) { - UserActivityEventHandler::getInstance()->removeEvents( - $this->objectType->objectType . '.response.recentActivityEvent', - $this->responseIDs - ); - } - } - - private function deleteNotifications(): void - { - if (UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.notification')) { - UserNotificationHandler::getInstance()->removeNotifications( - $this->objectType->objectType . '.response.notification', - $this->responseIDs - ); - } - } - - private function deleteReactions(): void - { - ReactionHandler::getInstance()->removeReactions( - 'com.woltlab.wcf.comment.response', - $this->responseIDs, - UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.response.like.notification') - ? [$this->objectType->objectType . '.response.like.notification'] - : [] - ); - } - - private function deleteModerationQueues(): void - { - ModerationQueueManager::getInstance()->removeQueues( - 'com.woltlab.wcf.comment.response', - $this->responseIDs - ); - } - - private function deleteMessageEmbeddedObjects(): void - { - MessageEmbeddedObjectManager::getInstance()->removeObjects( - 'com.woltlab.wcf.comment.response', - $this->responseIDs - ); - } - - private function updateCounters(): void - { - if (!$this->updateCounters) { - return; - } - - $commentIDs = []; - foreach ($this->responses as $response) { - $commentIDs[] = $response->commentID; - } - - $commentList = new CommentList(); - $commentList->setObjectIDs(\array_unique($commentIDs)); - $commentList->readObjects(); - ///** @var array */ - $comments = $commentList->getObjects(); - - foreach ($comments as $comment) { - $commentEditor = new CommentEditor($comment); - $commentEditor->updateResponseIDs(); - $commentEditor->updateUnfilteredResponseIDs(); - $commentEditor->updateResponses(); - $commentEditor->updateUnfilteredResponses(); - } - - foreach ($this->responses as $response) { - if (!$response->isDisabled) { - $this->commentManager->updateCounter($comments[$response->commentID]->objectID, -1); - } - } + (new \wcf\command\comment\response\DeleteResponses( + $this->responses, + $this->updateCounters + ))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/response/command/PublishResponse.class.php b/wcfsetup/install/files/lib/system/comment/response/command/PublishResponse.class.php index 459370ec6a7..6b4657b890c 100644 --- a/wcfsetup/install/files/lib/system/comment/response/command/PublishResponse.class.php +++ b/wcfsetup/install/files/lib/system/comment/response/command/PublishResponse.class.php @@ -2,20 +2,7 @@ namespace wcf\system\comment\response\command; -use wcf\data\comment\Comment; -use wcf\data\comment\CommentEditor; use wcf\data\comment\response\CommentResponse; -use wcf\data\comment\response\CommentResponseEditor; -use wcf\data\object\type\ObjectType; -use wcf\event\comment\response\ResponsePublished; -use wcf\system\comment\CommentHandler; -use wcf\system\comment\manager\ICommentManager; -use wcf\system\event\EventHandler; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\notification\object\CommentResponseUserNotificationObject; -use wcf\system\user\notification\object\type\ICommentUserNotificationObjectType; -use wcf\system\user\notification\object\type\IMultiRecipientCommentUserNotificationObjectType; -use wcf\system\user\notification\UserNotificationHandler; /** * Publishes a comment response. @@ -24,122 +11,18 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\response\PublishResponse` instead. */ final class PublishResponse { - private readonly ObjectType $objectType; - private readonly ICommentManager $commentManager; - private readonly Comment $comment; - public function __construct( private readonly CommentResponse $response, - ) { - $this->comment = $response->getComment(); - $this->objectType = CommentHandler::getInstance()->getObjectType($this->comment->objectTypeID); - $this->commentManager = CommentHandler::getInstance()->getCommentManagerByID($this->comment->objectTypeID); - } + ) {} public function __invoke(): void { - if ($this->response->isDisabled) { - (new CommentResponseEditor($this->response))->update([ - 'isDisabled' => 0, - ]); - } - - $commentEditor = new CommentEditor($this->comment); - $commentEditor->updateCounters(['responses' => 1]); - // do not prepend the response id as the approved response can appear anywhere - $commentEditor->updateResponseIDs(); - - $this->commentManager->updateCounter($this->comment->objectID, 1); - - $this->fireActivityEvent(); - $this->fireNotificationEvent(); - - $event = new ResponsePublished($this->response); - EventHandler::getInstance()->fire($event); - } - - private function fireActivityEvent(): void - { - if ( - $this->response->userID - && UserActivityEventHandler::getInstance()->getObjectTypeID( - $this->objectType->objectType . '.response.recentActivityEvent' - ) - ) { - UserActivityEventHandler::getInstance()->fireEvent( - $this->objectType->objectType . '.response.recentActivityEvent', - $this->response->responseID, - null, - $this->response->userID, - $this->response->time - ); - } - } - - private function fireNotificationEvent(): void - { - if ( - !UserNotificationHandler::getInstance()->getObjectTypeID($this->objectType->objectType . '.notification') - || ( - !UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponse') - && !UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponseOwner') - ) - ) { - return; - } - - $notificationObject = new CommentResponseUserNotificationObject($this->response); - $notificationObjectType = UserNotificationHandler::getInstance()->getObjectTypeProcessor($this->objectType->objectType . '.notification'); - - if ($notificationObjectType instanceof IMultiRecipientCommentUserNotificationObjectType) { - $recipientIDs = $notificationObjectType->getRecipientIDs($this->comment); - } else { - $recipientIDs = []; - } - - $recipientIDs[] = $this->comment->userID; - - $userID = 0; - if ($notificationObjectType instanceof ICommentUserNotificationObjectType) { - $userID = $notificationObjectType->getOwnerID($this->comment->commentID); - } - - // make sure that the response's author gets no notification - $recipientIDs = \array_diff($recipientIDs, [$this->response->getUserID()]); - - if (UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponse')) { - UserNotificationHandler::getInstance()->fireEvent( - 'commentResponse', - $this->objectType->objectType . '.response.notification', - $notificationObject, - $recipientIDs, - [ - 'commentID' => $this->comment->commentID, - 'objectID' => $this->comment->objectID, - 'userID' => $this->comment->userID, - ] - ); - } - - // notify the container owner - if (UserNotificationHandler::getInstance()->getEvent($this->objectType->objectType . '.response.notification', 'commentResponseOwner')) { - if ($userID && $userID != $this->comment->userID && $userID != $this->response->getUserID()) { - UserNotificationHandler::getInstance()->fireEvent( - 'commentResponseOwner', - $this->objectType->objectType . '.response.notification', - $notificationObject, - [$userID], - [ - 'commentID' => $this->comment->commentID, - 'objectID' => $this->comment->objectID, - 'objectUserID' => $userID, - 'userID' => $this->comment->userID, - ] - ); - } - } + (new \wcf\command\comment\response\PublishResponse( + $this->response, + ))(); } } diff --git a/wcfsetup/install/files/lib/system/comment/response/command/UpdateResponse.class.php b/wcfsetup/install/files/lib/system/comment/response/command/UpdateResponse.class.php index d7f369bd9eb..0d5561f60d2 100644 --- a/wcfsetup/install/files/lib/system/comment/response/command/UpdateResponse.class.php +++ b/wcfsetup/install/files/lib/system/comment/response/command/UpdateResponse.class.php @@ -3,11 +3,7 @@ namespace wcf\system\comment\response\command; use wcf\data\comment\response\CommentResponse; -use wcf\data\comment\response\CommentResponseAction; -use wcf\event\comment\response\ResponseUpdated; -use wcf\system\event\EventHandler; use wcf\system\html\input\HtmlInputProcessor; -use wcf\system\message\embedded\object\MessageEmbeddedObjectManager; /** * Updates a comment response. @@ -16,6 +12,7 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\comment\response\UpdateResponse` instead. */ final class UpdateResponse { @@ -26,22 +23,9 @@ public function __construct( public function __invoke(): void { - $data = [ - 'message' => $this->htmlInputProcessor->getHtml(), - ]; - - $this->htmlInputProcessor->setObjectID($this->response->getObjectID()); - $hasEmbeddedObjects = MessageEmbeddedObjectManager::getInstance()->registerObjects($this->htmlInputProcessor); - if ($this->response->hasEmbeddedObjects != $hasEmbeddedObjects) { - $data['hasEmbeddedObjects'] = $this->response->hasEmbeddedObjects ? 0 : 1; - } - - $action = new CommentResponseAction([$this->response], 'update', [ - 'data' => $data, - ]); - $action->executeAction(); - - $event = new ResponseUpdated(new CommentResponse($this->response->responseID)); - EventHandler::getInstance()->fire($event); + (new \wcf\command\comment\response\UpdateResponse( + $this->response, + $this->htmlInputProcessor + ))(); } } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/CreateComment.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/CreateComment.class.php index 49b3ffd0c08..a48dc16fa63 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/CreateComment.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/CreateComment.class.php @@ -73,7 +73,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $isDisabled = true; } - $comment = (new \wcf\system\comment\command\CreateComment( + $comment = (new \wcf\command\comment\CreateComment( $objectType, $parameters->objectID, $htmlInputProcessor, diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/DeleteComment.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/DeleteComment.class.php index d833f9123b2..c1293e64914 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/DeleteComment.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/DeleteComment.class.php @@ -30,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertCommentIsDeletable($comment); - (new \wcf\system\comment\command\DeleteComments([$comment]))(); + (new \wcf\command\comment\DeleteComments([$comment]))(); return new JsonResponse([]); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/EnableComment.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/EnableComment.class.php index 796586c48d3..3992c931c47 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/EnableComment.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/EnableComment.class.php @@ -31,7 +31,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertCommentCanBeEnabled($comment); if ($comment->isDisabled) { - (new \wcf\system\comment\command\PublishComment($comment))(); + (new \wcf\command\comment\PublishComment($comment))(); } return new JsonResponse([]); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/UpdateComment.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/UpdateComment.class.php index f22a15d7256..760829d769c 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/UpdateComment.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/UpdateComment.class.php @@ -50,7 +50,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res throw new PermissionDeniedException(); } - (new \wcf\system\comment\command\UpdateComment( + (new \wcf\command\comment\UpdateComment( $comment, $htmlInputProcessor, ))(); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/CreateResponse.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/CreateResponse.class.php index 8486f9d1933..59144d09658 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/CreateResponse.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/CreateResponse.class.php @@ -70,7 +70,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $isDisabled = true; } - $response = (new \wcf\system\comment\response\command\CreateResponse( + $response = (new \wcf\command\comment\response\CreateResponse( $comment, $htmlInputProcessor, WCF::getUser()->userID ? WCF::getUser() : null, diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/DeleteResponse.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/DeleteResponse.class.php index 4d91503507f..5e4e8a7e86b 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/DeleteResponse.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/DeleteResponse.class.php @@ -30,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertResponseIsDeletable($response); - (new \wcf\system\comment\response\command\DeleteResponses([$response]))(); + (new \wcf\command\comment\response\DeleteResponses([$response]))(); return new JsonResponse([]); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/UpdateResponse.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/UpdateResponse.class.php index 3d4f6a825cb..30f49fd4c1a 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/UpdateResponse.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/comments/responses/UpdateResponse.class.php @@ -51,7 +51,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res throw new PermissionDeniedException(); } - (new \wcf\system\comment\response\command\UpdateResponse( + (new \wcf\command\comment\response\UpdateResponse( $response, $htmlInputProcessor, ))(); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/AddDarkMode.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/AddDarkMode.class.php index 8026e8a34fe..ce18445b1d7 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/AddDarkMode.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/AddDarkMode.class.php @@ -10,7 +10,6 @@ use wcf\system\endpoint\IController; use wcf\system\endpoint\PostRequest; use wcf\system\exception\IllegalLinkException; -use wcf\system\style\command\AddDarkMode as AddDarkModeCommand; use wcf\system\WCF; /** @@ -31,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertDarkModeCanBeAdded($style); - $command = new AddDarkModeCommand($style); + $command = new \wcf\command\style\AddDarkMode($style); $command(); return new JsonResponse([]); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/CopyStyle.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/CopyStyle.class.php index 7ab6a845cfd..79718867d38 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/CopyStyle.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/styles/CopyStyle.class.php @@ -9,7 +9,6 @@ use wcf\http\Helper; use wcf\system\endpoint\IController; use wcf\system\endpoint\PostRequest; -use wcf\system\style\command\CopyStyle as CopyStyleCommand; use wcf\system\WCF; /** @@ -30,8 +29,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertStyleCanBeCopied(); - $command = new CopyStyleCommand($style); - $command(); + (new \wcf\command\style\CopyStyle($style))(); return new JsonResponse([]); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DeleteOption.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DeleteOption.class.php index 1426661064d..00dcfb594cc 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DeleteOption.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DeleteOption.class.php @@ -30,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertOptionCanBeDeleted($option); - (new \wcf\system\user\option\command\DeleteOption($option))(); + (new \wcf\command\user\option\DeleteOption($option))(); return new JsonResponse([]); } diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DisableOption.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DisableOption.class.php index f685c1cccb0..2c05f343341 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DisableOption.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/DisableOption.class.php @@ -30,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertOptionCanBeDisabled(); if (!$option->isDisabled) { - (new \wcf\system\user\option\command\DisableOption($option))(); + (new \wcf\command\user\option\DisableOption($option))(); } return new JsonResponse([]); diff --git a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/EnableOption.class.php b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/EnableOption.class.php index f2103491e93..4ca23a3e979 100644 --- a/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/EnableOption.class.php +++ b/wcfsetup/install/files/lib/system/endpoint/controller/core/users/options/EnableOption.class.php @@ -30,7 +30,7 @@ public function __invoke(ServerRequestInterface $request, array $variables): Res $this->assertOptionCanBeEnabled(); if ($option->isDisabled) { - (new \wcf\system\user\option\command\EnableOption($option))(); + (new \wcf\command\user\option\EnableOption($option))(); } return new JsonResponse([]); diff --git a/wcfsetup/install/files/lib/system/event/listener/PhraseChangedPreloadListener.class.php b/wcfsetup/install/files/lib/system/event/listener/PhraseChangedPreloadListener.class.php index cf874e43275..1a97077e098 100644 --- a/wcfsetup/install/files/lib/system/event/listener/PhraseChangedPreloadListener.class.php +++ b/wcfsetup/install/files/lib/system/event/listener/PhraseChangedPreloadListener.class.php @@ -6,7 +6,7 @@ use wcf\event\language\PhraseChanged; use wcf\event\language\PreloadPhrasesCollecting; use wcf\system\event\EventHandler; -use wcf\system\language\preload\command\ResetPreloadCache; +use wcf\command\language\preload\ResetPreloadCache; /** * Resets the preload cache if the modified phrase is diff --git a/wcfsetup/install/files/lib/system/event/listener/PipSyncedPhrasePreloadListener.class.php b/wcfsetup/install/files/lib/system/event/listener/PipSyncedPhrasePreloadListener.class.php index a0e11cbc24f..4219612691f 100644 --- a/wcfsetup/install/files/lib/system/event/listener/PipSyncedPhrasePreloadListener.class.php +++ b/wcfsetup/install/files/lib/system/event/listener/PipSyncedPhrasePreloadListener.class.php @@ -4,7 +4,7 @@ use wcf\event\package\PackageInstallationPluginSynced; use wcf\system\language\LanguageFactory; -use wcf\system\language\preload\command\ResetPreloadCache; +use wcf\command\language\preload\ResetPreloadCache; /** * Resets the preload cache when certain PIPs have been synced. diff --git a/wcfsetup/install/files/lib/system/file/command/ReplaceFileSource.class.php b/wcfsetup/install/files/lib/system/file/command/ReplaceFileSource.class.php index 00621148986..7df13de998b 100644 --- a/wcfsetup/install/files/lib/system/file/command/ReplaceFileSource.class.php +++ b/wcfsetup/install/files/lib/system/file/command/ReplaceFileSource.class.php @@ -3,13 +3,7 @@ namespace wcf\system\file\command; use wcf\data\file\File; -use wcf\data\file\FileEditor; -use wcf\system\file\processor\exception\DamagedImage; -use wcf\system\file\processor\FileProcessor; use wcf\system\WCF; -use wcf\util\FileUtil; - -use function wcf\functions\exception\logThrowable; /** * Replaces the physical file of an entry with a new file. If there are any @@ -19,6 +13,7 @@ * @copyright 2001-2025 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\file\ReplaceFileSource` instead. */ final class ReplaceFileSource { @@ -31,177 +26,11 @@ public function __construct( public function __invoke(): File { - $this->validatePathname($this->pathname); - $this->validatePathname($this->file->getPathname()); - $this->validatePathname($this->file->getPathnameWebp()); - - $file = $this->replaceSource(); - $file = $this->discardWebpVariantOfWebpFile($file); - - if ($this->regenerateThumbnails) { - $file = $this->regenerateExistingThumbnails($file); - } - - return $file; - } - - private function validatePathname(?string $pathname): void - { - if ($pathname === null) { - return; - } - - if (!\file_exists($pathname)) { - throw new \RuntimeException("The file '{$pathname}' does not exist."); - } - - if (!\is_writable($pathname)) { - throw new \RuntimeException("The file '{$pathname}' is not writable."); - } - } - - private function replaceSource(): File - { - $mimeType = FileUtil::getMimeType($this->pathname); - $isImage = match ($mimeType) { - 'image/gif' => true, - 'image/jpeg' => true, - 'image/png' => true, - 'image/webp' => true, - default => false, - }; - - $width = $height = null; - if ($isImage) { - [$width, $height] = \getimagesize($this->pathname); - } - - $filename = $this->filename ? $this->filename : \basename($this->pathname); - $fileSize = \filesize($this->pathname); - $fileHash = \hash_file('sha256', $this->pathname); - $fileExtension = File::getSafeFileExtension($mimeType, $filename); - - // The following code uses a transaction and a write lock on the file in - // order to guarantee this operation to atomic. The replacement is most - // likely the result of a post-processing step of the file thus is a - // repeatable action. - // - // If the rename fails for whatever reasons, then this file becomes - // corrupted without being able to recover the old state. This guard - // code prevents this from happening. - WCF::getDB()->beginTransaction(); - $committed = false; - try { - $sql = "SELECT * - FROM wcf1_file - WHERE fileID = ? - FOR UPDATE"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([$this->file->fileID]); - - (new FileEditor($this->file))->update([ - 'filename' => $filename, - 'fileSize' => $fileSize, - 'fileHash' => $fileHash, - 'fileExtension' => $fileExtension, - 'mimeType' => $mimeType, - 'width' => $width, - 'height' => $height, - ]); - - $updatedFile = new File($this->file->fileID); - - $path = \dirname($updatedFile->getPathname()); - FileUtil::makePath($path); - - \rename( - $this->pathname, - $updatedFile->getPathname(), - ); - - // Remove the previous file source because the new filename could - // differ due to the checksums. - if ($this->file->getPathname() !== $updatedFile->getPathname()) { - \unlink($this->file->getPathname()); - } - - // Move the WebP thumbnail to the new location. - $webpVariant = $this->file->getPathnameWebp(); - if ($webpVariant !== null) { - if ($updatedFile->mimeType === 'image/webp') { - // The file might be missing if it was used to replace the - // original version with it. - @\unlink($webpVariant); - - (new FileEditor($updatedFile))->update([ - 'fileHashWebp' => null, - ]); - $updatedFile = new File($updatedFile->fileID); - } else { - $newWebpVariant = $updatedFile->getPathnameWebp(); - \assert($newWebpVariant !== null); - - \rename( - $webpVariant, - $newWebpVariant, - ); - } - } - - WCF::getDB()->commitTransaction(); - $committed = true; - - return $updatedFile; - } finally { - if (!$committed) { - WCF::getDB()->rollBackTransaction(); - } - } - } - - private function discardWebpVariantOfWebpFile(File $file): File - { - if ($file->mimeType !== 'image/webp') { - return $file; - } - - $pathname = $file->getPathnameWebp(); - if ($pathname === null) { - return $file; - } - - if (\file_exists($pathname)) { - \unlink($file->getPathnameWebp()); - } - - (new FileEditor($file))->update([ - 'fileHashWebp' => null, - ]); - - return new File($file->fileID); - } - - private function regenerateExistingThumbnails(File $file): File - { - $sql = "SELECT COUNT(*) - FROM wcf1_file_thumbnail - WHERE fileID = ?"; - $statement = WCF::getDB()->prepare($sql); - $statement->execute([$file->fileID]); - $count = $statement->fetchSingleColumn(); - if ($count === 0) { - return $file; - } - - try { - $file = FileProcessor::getInstance()->generateWebpVariant($file); - $file = FileProcessor::getInstance()->stripExif($file); - $file = FileProcessor::getInstance()->convertImageFormat($file); - FileProcessor::getInstance()->generateThumbnails($file); - } catch (DamagedImage $e) { - logThrowable($e); - } finally { - return $file; - } + return (new \wcf\command\file\ReplaceFileSource( + $this->file, + $this->pathname, + $this->filename, + $this->regenerateThumbnails + ))(); } } diff --git a/wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php b/wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php index 6a6a39691b6..0f6b264e221 100644 --- a/wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php +++ b/wcfsetup/install/files/lib/system/file/processor/FileProcessor.class.php @@ -13,8 +13,8 @@ use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; -use wcf\system\file\command\ReplaceFileSource; -use wcf\system\file\command\ReplaceWithWebpVariant; +use wcf\command\file\ReplaceFileSource; +use wcf\command\file\ReplaceWithWebpVariant; use wcf\system\file\processor\exception\DamagedImage; use wcf\system\image\adapter\exception\ImageNotProcessable; use wcf\system\image\adapter\exception\ImageNotReadable; diff --git a/wcfsetup/install/files/lib/system/file/processor/UserAvatarFileProcessor.class.php b/wcfsetup/install/files/lib/system/file/processor/UserAvatarFileProcessor.class.php index af7efaf8cb0..10348013503 100644 --- a/wcfsetup/install/files/lib/system/file/processor/UserAvatarFileProcessor.class.php +++ b/wcfsetup/install/files/lib/system/file/processor/UserAvatarFileProcessor.class.php @@ -8,7 +8,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache; use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\exception\UserInputException; -use wcf\system\user\command\SetAvatar; +use wcf\command\user\SetAvatar; use wcf\system\WCF; use wcf\util\FileUtil; diff --git a/wcfsetup/install/files/lib/system/file/processor/UserCoverPhotoFileProcessor.class.php b/wcfsetup/install/files/lib/system/file/processor/UserCoverPhotoFileProcessor.class.php index 88f27e46b4f..c9ea958055b 100644 --- a/wcfsetup/install/files/lib/system/file/processor/UserCoverPhotoFileProcessor.class.php +++ b/wcfsetup/install/files/lib/system/file/processor/UserCoverPhotoFileProcessor.class.php @@ -8,7 +8,7 @@ use wcf\system\cache\runtime\UserProfileRuntimeCache; use wcf\system\database\util\PreparedStatementConditionBuilder; use wcf\system\exception\UserInputException; -use wcf\system\user\command\SetCoverPhoto; +use wcf\command\user\SetCoverPhoto; use wcf\system\WCF; use wcf\util\FileUtil; @@ -161,8 +161,8 @@ public function canDelete(File $file): bool $user = $this->getUserByFile($file); if ($user === null) { return WCF::getSession()->getVar( - \sprintf(self::SESSION_VARIABLE, $file->fileID) - ) !== null; + \sprintf(self::SESSION_VARIABLE, $file->fileID) + ) !== null; } return $user->canEditCoverPhoto(); diff --git a/wcfsetup/install/files/lib/system/language/preload/PhrasePreloader.class.php b/wcfsetup/install/files/lib/system/language/preload/PhrasePreloader.class.php index a7f7ce1852d..82f4421c586 100644 --- a/wcfsetup/install/files/lib/system/language/preload/PhrasePreloader.class.php +++ b/wcfsetup/install/files/lib/system/language/preload/PhrasePreloader.class.php @@ -3,7 +3,7 @@ namespace wcf\system\language\preload; use wcf\data\language\Language; -use wcf\system\language\preload\command\CachePreloadPhrases; +use wcf\command\language\preload\CachePreloadPhrases; use wcf\system\WCF; /** diff --git a/wcfsetup/install/files/lib/system/language/preload/command/CachePreloadPhrases.class.php b/wcfsetup/install/files/lib/system/language/preload/command/CachePreloadPhrases.class.php index 79df0a23139..a754ba96977 100644 --- a/wcfsetup/install/files/lib/system/language/preload/command/CachePreloadPhrases.class.php +++ b/wcfsetup/install/files/lib/system/language/preload/command/CachePreloadPhrases.class.php @@ -3,67 +3,27 @@ namespace wcf\system\language\preload\command; use wcf\data\language\Language; -use wcf\event\language\PreloadPhrasesCollecting; -use wcf\system\event\EventHandler; -use wcf\system\io\AtomicWriter; -use wcf\util\StringUtil; /** * Rebuilds the phrase preload cache for the * requested language. * - * @author Alexander Ebert - * @copyright 2001-2022 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @author Alexander Ebert + * @copyright 2001-2022 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\language\preload\CachePreloadPhrases` instead. */ final class CachePreloadPhrases { - private readonly EventHandler $eventHandler; - - private readonly Language $language; - - public function __construct(Language $language) - { - $this->language = $language; - - $this->eventHandler = EventHandler::getInstance(); - } + public function __construct( + private readonly Language $language + ) {} public function __invoke(): void { - $event = new PreloadPhrasesCollecting($this->language); - $this->eventHandler->fire($event); - - $file = new AtomicWriter(\WCF_DIR . $this->language->getPreloadCacheFilename()); - $file->write( - \sprintf( - "/* cache for '%s' (generated at %s) -- DO NOT EDIT */\n", - $this->language->getLocale(), - (new \DateTimeImmutable())->format('c'), - ) - ); - - foreach ($event->getPhrases() as $phrase) { - $file->write( - \sprintf( - "WoltLabLanguage.registerPhrase('%s', '%s');\n", - $phrase, - $this->getEncodedValue($phrase), - ) - ); - } - - // Add a distinct marker at the end to prove - // that the file was fully written. - $file->write("/* EOF */\n"); - - $file->flush(); - $file->close(); - } - - private function getEncodedValue(string $phrase): string - { - return StringUtil::encodeJS($this->language->get($phrase)); + (new \wcf\command\language\preload\CachePreloadPhrases( + $this->language + ))(); } } diff --git a/wcfsetup/install/files/lib/system/language/preload/command/ResetPreloadCache.class.php b/wcfsetup/install/files/lib/system/language/preload/command/ResetPreloadCache.class.php index 08576a38555..c2764959a52 100644 --- a/wcfsetup/install/files/lib/system/language/preload/command/ResetPreloadCache.class.php +++ b/wcfsetup/install/files/lib/system/language/preload/command/ResetPreloadCache.class.php @@ -7,26 +7,22 @@ /** * Resets the preload cache for the requested language. * - * @author Alexander Ebert - * @copyright 2001-2022 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @author Alexander Ebert + * @copyright 2001-2022 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\language\preload\ResetPreloadCache` instead. */ final class ResetPreloadCache { - private readonly Language $language; - - public function __construct(Language $language) - { - $this->language = $language; - } + public function __construct( + private readonly Language $language + ) {} public function __invoke(): void { - // Try to remove the file if it exists. - $filename = \WCF_DIR . $this->language->getPreloadCacheFilename(); - if (\file_exists($filename)) { - \unlink($filename); - } + (new \wcf\command\language\preload\ResetPreloadCache( + $this->language + ))(); } } diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php index 078078d441e..42af39f62e0 100644 --- a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php +++ b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentCommentModerationQueueHandler.class.php @@ -149,7 +149,7 @@ public function populate(array $queues) public function removeContent(ModerationQueue $queue, $message) { if ($this->isValid($queue->objectID)) { - (new \wcf\system\comment\command\DeleteComments([$this->getComment($queue->objectID)]))(); + (new \wcf\command\comment\DeleteComments([$this->getComment($queue->objectID)]))(); } } diff --git a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php index 83abf88fbc0..a91a0de49bd 100644 --- a/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php +++ b/wcfsetup/install/files/lib/system/moderation/queue/AbstractCommentResponseModerationQueueHandler.class.php @@ -158,7 +158,7 @@ public function populate(array $queues) public function removeContent(ModerationQueue $queue, $message) { if ($this->isValid($queue->objectID)) { - (new \wcf\system\comment\response\command\DeleteResponses([$this->getResponse($queue->objectID)]))(); + (new \wcf\command\comment\response\DeleteResponses([$this->getResponse($queue->objectID)]))(); } } diff --git a/wcfsetup/install/files/lib/system/moderation/queue/command/AssignUser.class.php b/wcfsetup/install/files/lib/system/moderation/queue/command/AssignUser.class.php index 37f343f1645..f905cf382f8 100644 --- a/wcfsetup/install/files/lib/system/moderation/queue/command/AssignUser.class.php +++ b/wcfsetup/install/files/lib/system/moderation/queue/command/AssignUser.class.php @@ -3,79 +3,29 @@ namespace wcf\system\moderation\queue\command; use wcf\data\moderation\queue\ModerationQueue; -use wcf\data\moderation\queue\ModerationQueueEditor; use wcf\data\user\User; -use wcf\event\moderation\queue\UserAssigned; -use wcf\system\event\EventHandler; /** * Assigns a user to a moderation queue entry. * - * @author Tim Duesterhus + * @author Tim Duesterhus * @copyright 2001-2022 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\moderation\queue\AssignUser` instead. */ final class AssignUser { - private EventHandler $eventHandler; - - private int $moderationQueueId; - - /** - * The user the queue entry should be assigned to. null to remove the assignment. - */ - private ?int $userId; - public function __construct( - ModerationQueue $moderationQueue, - ?User $user, - ) { - $this->moderationQueueId = $moderationQueue->queueID; - $this->userId = $user?->userID; - - $this->eventHandler = EventHandler::getInstance(); - } + private readonly ModerationQueue $moderationQueue, + private readonly ?User $user, + ) {} public function __invoke(): void { - $moderationQueueEditor = new ModerationQueueEditor(new ModerationQueue($this->moderationQueueId)); - if ($this->userId !== null) { - $user = new User($this->userId); - } else { - $user = null; - } - - $oldAssignee = $moderationQueueEditor->assignedUserID ? new User($moderationQueueEditor->assignedUserID) : null; - - // If the old assignee matches the new assignee, we do not need to - // do anything. - if ($oldAssignee?->userID === $user?->userID) { - return; - } - - $data = [ - 'assignedUserID' => $user?->userID, - ]; - - if ($user !== null) { - if ($moderationQueueEditor->status == ModerationQueue::STATUS_OUTSTANDING) { - $data['status'] = ModerationQueue::STATUS_PROCESSING; - } - } else { - if ($moderationQueueEditor->status == ModerationQueue::STATUS_PROCESSING) { - $data['status'] = ModerationQueue::STATUS_OUTSTANDING; - } - } - - $moderationQueueEditor->update($data); - - $this->eventHandler->fire( - new UserAssigned( - $moderationQueueEditor->getDecoratedObject(), - $user, - $oldAssignee - ) - ); + (new \wcf\command\moderation\queue\AssignUser( + $this->moderationQueue, + $this->user + ))(); } } diff --git a/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php b/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php index 1d713d3bdd4..a0a0433ae58 100644 --- a/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php +++ b/wcfsetup/install/files/lib/system/package/PackageInstallationDispatcher.class.php @@ -15,7 +15,7 @@ use wcf\data\user\UserAction; use wcf\event\package\PackageListChanged; use wcf\system\application\ApplicationHandler; -use wcf\system\cache\command\ClearCache; +use wcf\command\cache\ClearCache; use wcf\system\database\statement\PreparedStatement; use wcf\system\devtools\DevtoolsSetup; use wcf\system\Environment; @@ -29,7 +29,6 @@ use wcf\system\form\element\TextInputFormElement; use wcf\system\form\FormDocument; use wcf\system\language\LanguageFactory; -use wcf\system\package\command\RebuildBootstrapper; use wcf\system\package\plugin\IPackageInstallationPlugin; use wcf\system\registry\RegistryHandler; use wcf\system\request\RouteHandler; @@ -217,7 +216,7 @@ public function install(string $node): PackageInstallationStep VersionTracker::getInstance()->createStorageTables(); - $command = new RebuildBootstrapper(); + $command = new \wcf\command\package\RebuildBootstrapper(); $command(); EventHandler::getInstance()->fire(new PackageListChanged()); diff --git a/wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php b/wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php index 71b1a58028e..8422d14a890 100644 --- a/wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php +++ b/wcfsetup/install/files/lib/system/package/PackageUninstallationDispatcher.class.php @@ -7,9 +7,8 @@ use wcf\event\package\PackageListChanged; use wcf\system\application\ApplicationHandler; use wcf\system\cache\builder\PackageCacheBuilder; -use wcf\system\cache\command\ClearCache; +use wcf\command\cache\ClearCache; use wcf\system\event\EventHandler; -use wcf\system\package\command\RebuildBootstrapper; use wcf\system\package\plugin\IPackageInstallationPlugin; use wcf\system\setup\Uninstaller; use wcf\system\WCF; @@ -78,7 +77,7 @@ public function uninstall(string $node): PackageInstallationStep $step = $this->executePIP($nodeData); if ($nodeData['pluginName'] == 'file') { - $command = new RebuildBootstrapper(); + $command = new \wcf\command\package\RebuildBootstrapper(); $command(); } break; diff --git a/wcfsetup/install/files/lib/system/package/command/RebuildBootstrapper.class.php b/wcfsetup/install/files/lib/system/package/command/RebuildBootstrapper.class.php index d6fc9ccf537..27f07a37633 100644 --- a/wcfsetup/install/files/lib/system/package/command/RebuildBootstrapper.class.php +++ b/wcfsetup/install/files/lib/system/package/command/RebuildBootstrapper.class.php @@ -2,91 +2,21 @@ namespace wcf\system\package\command; -use wcf\data\package\Package; -use wcf\data\package\PackageList; -use wcf\system\io\AtomicWriter; use wcf\system\WCF; /** * Rebuilds the bootstrapping script. * - * @author Tim Duesterhus + * @author Tim Duesterhus * @copyright 2001-2021 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\package\RebuildBootstrapper` instead. */ final class RebuildBootstrapper { public function __invoke(): void { - $groups = PackageList::getTopologicallySortedPackages(); - - $now = new \DateTimeImmutable('now', new \DateTimeZone('UTC')); - - $result = "format('c')} */\n\n"; - $result .= <<<'EOT' - return (function() { - if (\ENABLE_DEBUG_MODE) { - $shuffle = static function (array $array) { - \shuffle($array); - - return $array; - }; - } else { - $shuffle = static function (array $array) { - return $array; - }; - } - - return [ - EOT; - $result .= "\n"; - - foreach ($groups as $group) { - $group = \array_values(\array_filter($group, $this->bootstrapExists(...))); - - if ($group === []) { - continue; - } - - if (\count($group) === 1) { - $package = $group[0]; - $result .= " require(__DIR__ . '/{$this->getRelativeBootstrapFilename($package)}'),\n"; - } else { - $result .= " ...\$shuffle([\n"; - \shuffle($group); - foreach ($group as $package) { - $result .= " require(__DIR__ . '/{$this->getRelativeBootstrapFilename($package)}'),\n"; - } - $result .= " ]),\n"; - } - } - - $result .= <<<'EOT' - ]; - })(); - EOT; - $result .= "\n"; - - $writer = new AtomicWriter(WCF::BOOTSTRAP_LOADER); - $writer->write($result); - $writer->flush(); - - WCF::resetZendOpcache(WCF::BOOTSTRAP_LOADER); - } - - private function bootstrapExists(Package $package): bool - { - return \file_exists($this->getBootstrapFilename($package)); - } - - private function getBootstrapFilename(Package $package): string - { - return \WCF_DIR . 'lib/' . $this->getRelativeBootstrapFilename($package); - } - - private function getRelativeBootstrapFilename(Package $package): string - { - return 'bootstrap/' . $package->package . '.php'; + (new \wcf\command\package\RebuildBootstrapper())(); } } diff --git a/wcfsetup/install/files/lib/system/style/StyleCompiler.class.php b/wcfsetup/install/files/lib/system/style/StyleCompiler.class.php index fbdcbffd529..073054b1cc2 100644 --- a/wcfsetup/install/files/lib/system/style/StyleCompiler.class.php +++ b/wcfsetup/install/files/lib/system/style/StyleCompiler.class.php @@ -12,7 +12,7 @@ use wcf\system\event\EventHandler; use wcf\system\exception\SystemException; use wcf\system\SingletonFactory; -use wcf\system\style\command\CreateManifest; +use wcf\command\style\CreateManifest; use wcf\system\WCF; use wcf\util\FileUtil; use wcf\util\JSON; diff --git a/wcfsetup/install/files/lib/system/style/command/AddDarkMode.class.php b/wcfsetup/install/files/lib/system/style/command/AddDarkMode.class.php index 04662b7c89d..ce4f581fecb 100644 --- a/wcfsetup/install/files/lib/system/style/command/AddDarkMode.class.php +++ b/wcfsetup/install/files/lib/system/style/command/AddDarkMode.class.php @@ -3,41 +3,26 @@ namespace wcf\system\style\command; use wcf\data\style\Style; -use wcf\data\style\StyleEditor; -use wcf\system\cache\builder\StyleCacheBuilder; -use wcf\system\style\StyleHandler; /** * Adds the dark color scheme to a style. * - * @author Alexander Ebert - * @copyright 2001-2023 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @author Alexander Ebert + * @copyright 2001-2023 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\style\AddDarkMode` instead. */ final class AddDarkMode { - private readonly int $styleID; - - private readonly StyleHandler $styleHandler; - - public function __construct(Style $style) - { - $this->styleID = $style->styleID; - - $this->styleHandler = StyleHandler::getInstance(); - } + public function __construct( + private readonly Style $style + ) {} public function __invoke(): void { - $styleEditor = new StyleEditor(new Style($this->styleID)); - $styleEditor->update([ - 'hasDarkMode' => 1, - ]); - - $style = new Style($this->styleID); - - StyleCacheBuilder::getInstance()->reset(); - $this->styleHandler->resetStylesheet($style); + (new \wcf\command\style\AddDarkMode( + $this->style + ))(); } } diff --git a/wcfsetup/install/files/lib/system/style/command/CreateManifest.class.php b/wcfsetup/install/files/lib/system/style/command/CreateManifest.class.php index 10f4a22c76d..8e9193bcf37 100644 --- a/wcfsetup/install/files/lib/system/style/command/CreateManifest.class.php +++ b/wcfsetup/install/files/lib/system/style/command/CreateManifest.class.php @@ -2,12 +2,8 @@ namespace wcf\system\style\command; -use wcf\data\page\PageCache; use wcf\data\style\Style; -use wcf\system\io\AtomicWriter; -use wcf\system\language\LanguageFactory; use wcf\system\WCF; -use wcf\util\JSON; /** * Generate then `manifest-*.json` files for a style. @@ -16,69 +12,18 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\style\CreateManifest` instead. */ final class CreateManifest { - private readonly Style $style; - - public function __construct(Style $style) - { - $this->style = $style; - } + public function __construct( + private readonly Style $style + ) {} public function __invoke(): void { - $this->style->loadVariables(); - $headerColor = $this->style->getVariable('wcfHeaderBackground', true); - $backgroundColor = $this->style->getVariable('wcfContentBackground', true); - $landingPage = PageCache::getInstance()->getLandingPage(); - - $icons = []; - foreach ([192, 256, 512] as $iconSize) { - $icons [] = [ - "src" => \sprintf( - "%sandroid-chrome-%dx%d.png", - $this->style->hasFavicon ? "" : "../favicon/default.", - $iconSize, - $iconSize - ), - "sizes" => "{$iconSize}x{$iconSize}", - "type" => "image/png" - ]; - } - $icons = JSON::encode($icons); - - $originalLanguage = WCF::getLanguage(); - try { - foreach (LanguageFactory::getInstance()->getLanguages() as $language) { - // To get the correct landing page url, we need to change the language. - WCF::setLanguage($language->languageID); - - $title = JSON::encode($language->get(PAGE_TITLE)); - $startUrl = JSON::encode($landingPage->getLink()); - - // update manifest.json - $manifest = <<style->getAssetPath() . "manifest-{$language->languageID}.json"; - if (\file_exists($manifestPath) && \hash_equals(\sha1_file($manifestPath), \sha1($manifest))) { - continue; - } - $writer = new AtomicWriter($manifestPath); - $writer->write($manifest); - $writer->flush(); - $writer->close(); - } - } finally { - WCF::setLanguage($originalLanguage->languageID); - } + (new \wcf\command\style\CreateManifest( + $this->style + ))(); } } diff --git a/wcfsetup/install/files/lib/system/user/command/CreateRegistrationNotification.class.php b/wcfsetup/install/files/lib/system/user/command/CreateRegistrationNotification.class.php index 694f064ff6d..f2b9f1cc269 100644 --- a/wcfsetup/install/files/lib/system/user/command/CreateRegistrationNotification.class.php +++ b/wcfsetup/install/files/lib/system/user/command/CreateRegistrationNotification.class.php @@ -3,8 +3,6 @@ namespace wcf\system\user\command; use wcf\data\user\User; -use wcf\system\user\notification\object\UserRegistrationUserNotificationObject; -use wcf\system\user\notification\UserNotificationHandler; use wcf\system\WCF; /** @@ -14,51 +12,18 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\user\CreateRegistrationNotification` instead. */ final class CreateRegistrationNotification { - public function __construct(private readonly User $user) {} + public function __construct( + private readonly User $user + ) {} public function __invoke(): void { - if ($this->user->requiresEmailActivation()) { - return; - } - - $recipientIDs = $this->getRecipientsForNotificationEvent(); - if (!empty($recipientIDs)) { - UserNotificationHandler::getInstance()->fireEvent( - $this->user->requiresAdminActivation() ? 'needActivation' : 'success', - 'com.woltlab.wcf.user.registration.notification', - new UserRegistrationUserNotificationObject($this->user), - $recipientIDs - ); - } - } - - /** - * @return int[] - */ - private function getRecipientsForNotificationEvent(): array - { - $sql = "SELECT userID - FROM wcf1_user_to_group - WHERE groupID IN ( - SELECT groupID - FROM wcf1_user_group_option_value - WHERE optionID IN ( - SELECT optionID - FROM wcf1_user_group_option - WHERE optionName = ? - ) - AND optionValue = ? - )"; - $statement = WCF::getDB()->prepare($sql, 100); - $statement->execute([ - 'admin.user.canSearchUser', - 1, - ]); - - return $statement->fetchAll(\PDO::FETCH_COLUMN); + (new \wcf\command\user\CreateRegistrationNotification( + $this->user + ))(); } } diff --git a/wcfsetup/install/files/lib/system/user/command/Follow.class.php b/wcfsetup/install/files/lib/system/user/command/Follow.class.php index 4e56a08dcd1..00d60c42942 100644 --- a/wcfsetup/install/files/lib/system/user/command/Follow.class.php +++ b/wcfsetup/install/files/lib/system/user/command/Follow.class.php @@ -2,13 +2,7 @@ namespace wcf\system\user\command; -use wcf\data\user\follow\UserFollow; -use wcf\data\user\follow\UserFollowEditor; use wcf\data\user\User; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\notification\object\UserFollowUserNotificationObject; -use wcf\system\user\notification\UserNotificationHandler; -use wcf\system\user\storage\UserStorageHandler; /** * Saves that a user is following another user. @@ -17,51 +11,20 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\user\Follow` instead. */ final class Follow { - public function __construct(private readonly User $user, private readonly User $target) - { - } + public function __construct( + private readonly User $user, + private readonly User $target + ) {} public function __invoke(): void { - $follow = UserFollowEditor::createOrIgnore([ - 'userID' => $this->user->userID, - 'followUserID' => $this->target->userID, - 'time' => TIME_NOW, - ]); - - if ($follow === null) { - return; - } - - $this->sendNotification($follow); - $this->fireActivityEvent(); - $this->resetUserStorage(); - } - - private function sendNotification(UserFollow $follow): void - { - UserNotificationHandler::getInstance()->fireEvent( - 'following', - 'com.woltlab.wcf.user.follow', - new UserFollowUserNotificationObject($follow), - [$follow->followUserID] - ); - } - - private function fireActivityEvent(): void - { - UserActivityEventHandler::getInstance()->fireEvent( - 'com.woltlab.wcf.user.recentActivityEvent.follow', - $this->target->userID - ); - } - - private function resetUserStorage(): void - { - UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs'); - UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs'); + (new \wcf\command\user\Follow( + $this->user, + $this->target + ))(); } } diff --git a/wcfsetup/install/files/lib/system/user/command/SetColorScheme.class.php b/wcfsetup/install/files/lib/system/user/command/SetColorScheme.class.php index 1e630a4d7c3..09870df872e 100644 --- a/wcfsetup/install/files/lib/system/user/command/SetColorScheme.class.php +++ b/wcfsetup/install/files/lib/system/user/command/SetColorScheme.class.php @@ -3,34 +3,25 @@ namespace wcf\system\user\command; use wcf\data\user\User; -use wcf\data\user\UserAction; /** * Sets the preferred color scheme of a user. * - * @author Alexander Ebert - * @copyright 2001-2023 WoltLab GmbH - * @license GNU Lesser General Public License - * @since 6.0 + * @author Alexander Ebert + * @copyright 2001-2023 WoltLab GmbH + * @license GNU Lesser General Public License + * @since 6.0 + * @deprecated 6.2 Use `\wcf\command\user\SetColorScheme` instead. */ final class SetColorScheme { - private readonly User $user; - private readonly string $colorScheme; - - public function __construct(User $user, string $colorScheme) - { - $this->user = $user; - $this->colorScheme = $colorScheme; - } + public function __construct(private readonly User $user, private readonly string $colorScheme) {} public function __invoke(): void { - $userAction = new UserAction([$this->user], 'update', [ - 'options' => [ - User::getUserOptionID('colorScheme') => $this->colorScheme, - ], - ]); - $userAction->executeAction(); + (new \wcf\command\user\SetColorScheme( + $this->user, + $this->colorScheme + ))(); } } diff --git a/wcfsetup/install/files/lib/system/user/command/Unfollow.class.php b/wcfsetup/install/files/lib/system/user/command/Unfollow.class.php index 0c01f98e2d0..1020a48ba50 100644 --- a/wcfsetup/install/files/lib/system/user/command/Unfollow.class.php +++ b/wcfsetup/install/files/lib/system/user/command/Unfollow.class.php @@ -2,11 +2,7 @@ namespace wcf\system\user\command; -use wcf\data\user\follow\UserFollow; -use wcf\data\user\follow\UserFollowEditor; use wcf\data\user\User; -use wcf\system\user\activity\event\UserActivityEventHandler; -use wcf\system\user\storage\UserStorageHandler; /** * Saves that a user is unfollowing another user. @@ -15,38 +11,20 @@ * @copyright 2001-2024 WoltLab GmbH * @license GNU Lesser General Public License * @since 6.1 + * @deprecated 6.2 Use `\wcf\command\user\Unfollow` instead. */ final class Unfollow { - public function __construct(private readonly User $user, private readonly User $target) - { - } + public function __construct( + private readonly User $user, + private readonly User $target + ) {} public function __invoke(): void { - $follow = UserFollow::getFollow($this->user->userID, $this->target->userID); - - if ($follow->followID) { - $followEditor = new UserFollowEditor($follow); - $followEditor->delete(); - - $this->removeActivityEvent(); - } - - $this->resetUserStorage(); - } - - private function removeActivityEvent(): void - { - UserActivityEventHandler::getInstance()->removeEvent( - 'com.woltlab.wcf.user.recentActivityEvent.follow', - $this->target->userID - ); - } - - private function resetUserStorage(): void - { - UserStorageHandler::getInstance()->reset([$this->target->userID], 'followerUserIDs'); - UserStorageHandler::getInstance()->reset([$this->user->userID], 'followingUserIDs'); + (new \wcf\command\user\Unfollow( + $this->user, + $this->target + ))(); } } diff --git a/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php index 14b504fc89b..8e3c95bf3d2 100644 --- a/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php +++ b/wcfsetup/install/files/lib/system/worker/UserRebuildDataWorker.class.php @@ -19,7 +19,7 @@ use wcf\system\file\processor\UserAvatarFileProcessor; use wcf\system\html\input\HtmlInputProcessor; use wcf\system\image\ImageHandler; -use wcf\system\user\command\SetCoverPhoto; +use wcf\command\user\SetCoverPhoto; use wcf\system\user\storage\UserStorageHandler; use wcf\system\WCF;