diff --git a/com.woltlab.wcf/objectType.xml b/com.woltlab.wcf/objectType.xml
index e23256e7b78..b5d91b07201 100644
--- a/com.woltlab.wcf/objectType.xml
+++ b/com.woltlab.wcf/objectType.xml
@@ -86,6 +86,11 @@
com.woltlab.wcf.moderation.report
wcf\system\moderation\queue\report\ArticleModerationQueueReportHandler
+
+ com.woltlab.wcf.article
+ com.woltlab.wcf.moderation.activation
+ wcf\system\moderation\queue\activation\ArticleModerationQueueActivationHandler
+
com.woltlab.wcf.article.category
com.woltlab.wcf.user.objectWatch
diff --git a/wcfsetup/install/files/lib/acp/form/ArticleAddForm.class.php b/wcfsetup/install/files/lib/acp/form/ArticleAddForm.class.php
index 70b19185c40..01e1be6fd7f 100644
--- a/wcfsetup/install/files/lib/acp/form/ArticleAddForm.class.php
+++ b/wcfsetup/install/files/lib/acp/form/ArticleAddForm.class.php
@@ -695,7 +695,10 @@ protected function getCommand(DatabaseObjectBuilder $builder): callable
return new UpdateArticle($builder);
}
- return new CreateArticle($builder);
+ $canManage = WCF::getSession()->hasPermission('admin.content.article.canManageArticle')
+ || WCF::getSession()->hasPermission('admin.content.article.canManageOwnArticles');
+
+ return new CreateArticle($builder, !$canManage);
}
protected function getAttachmentObjectID(?int $languageID = null): ?int
diff --git a/wcfsetup/install/files/lib/command/article/CreateArticle.class.php b/wcfsetup/install/files/lib/command/article/CreateArticle.class.php
index f8e9a107227..c78bcf0dd0a 100644
--- a/wcfsetup/install/files/lib/command/article/CreateArticle.class.php
+++ b/wcfsetup/install/files/lib/command/article/CreateArticle.class.php
@@ -6,6 +6,7 @@
use wcf\data\article\ArticleBuilder;
use wcf\event\article\ArticleCreated;
use wcf\system\event\EventHandler;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
use wcf\system\search\SearchIndexManager;
use wcf\system\user\activity\event\UserActivityEventHandler;
use wcf\system\user\notification\object\ArticleUserNotificationObject;
@@ -23,6 +24,7 @@ final class CreateArticle
{
public function __construct(
private readonly ArticleBuilder $builder,
+ private readonly bool $addToModerationQueue = false,
) {}
public function __invoke(): Article
@@ -56,6 +58,11 @@ public function __invoke(): Article
);
new MarkArticleAsRead($article)();
+ } elseif ($this->addToModerationQueue) {
+ ModerationQueueActivationManager::getInstance()->addModeratedContent(
+ 'com.woltlab.wcf.article',
+ $article->articleID
+ );
}
EventHandler::getInstance()->fire(new ArticleCreated($article, $this->builder));
diff --git a/wcfsetup/install/files/lib/command/article/DeleteArticle.class.php b/wcfsetup/install/files/lib/command/article/DeleteArticle.class.php
index 8934d149069..2e5d967d68b 100644
--- a/wcfsetup/install/files/lib/command/article/DeleteArticle.class.php
+++ b/wcfsetup/install/files/lib/command/article/DeleteArticle.class.php
@@ -10,6 +10,7 @@
use wcf\system\comment\CommentHandler;
use wcf\system\event\EventHandler;
use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
use wcf\system\search\SearchIndexManager;
use wcf\system\tagging\TagEngine;
use wcf\system\user\activity\event\UserActivityEventHandler;
@@ -69,6 +70,11 @@ public function __invoke(): void
ArticleBuilder::incrementArticleCounter($this->article->userID, -1);
}
}
+ // delete moderation queue entries
+ ModerationQueueActivationManager::getInstance()->removeModeratedContent(
+ 'com.woltlab.wcf.article',
+ [$this->article->articleID]
+ );
// delete attachments
if ($attachmentArticleContentIDs !== []) {
AttachmentHandler::removeAttachments(
diff --git a/wcfsetup/install/files/lib/command/article/PublishArticle.class.php b/wcfsetup/install/files/lib/command/article/PublishArticle.class.php
index 9c149ff06d2..d071ed0a6ed 100644
--- a/wcfsetup/install/files/lib/command/article/PublishArticle.class.php
+++ b/wcfsetup/install/files/lib/command/article/PublishArticle.class.php
@@ -6,6 +6,7 @@
use wcf\data\article\ArticleBuilder;
use wcf\event\article\ArticlePublished;
use wcf\system\event\EventHandler;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
use wcf\system\user\activity\event\UserActivityEventHandler;
use wcf\system\user\notification\object\ArticleUserNotificationObject;
use wcf\system\user\object\watch\UserObjectWatchHandler;
@@ -39,6 +40,11 @@ public function __invoke(): void
new ResetUserStorageForUnreadArticles()();
+ ModerationQueueActivationManager::getInstance()->removeModeratedContent(
+ 'com.woltlab.wcf.article',
+ [$this->article->articleID]
+ );
+
$event = new ArticlePublished($this->article);
EventHandler::getInstance()->fire($event);
}
diff --git a/wcfsetup/install/files/lib/command/article/SoftDeleteArticle.class.php b/wcfsetup/install/files/lib/command/article/SoftDeleteArticle.class.php
index 65b1728596a..8894035f452 100644
--- a/wcfsetup/install/files/lib/command/article/SoftDeleteArticle.class.php
+++ b/wcfsetup/install/files/lib/command/article/SoftDeleteArticle.class.php
@@ -6,6 +6,7 @@
use wcf\data\article\ArticleBuilder;
use wcf\event\article\ArticleSoftDeleted;
use wcf\system\event\EventHandler;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
/**
* Soft-deletes an article.
@@ -27,6 +28,11 @@ public function __invoke(): void
new ResetUserStorageForUnreadArticles()();
+ ModerationQueueActivationManager::getInstance()->removeModeratedContent(
+ 'com.woltlab.wcf.article',
+ [$this->article->articleID]
+ );
+
$event = new ArticleSoftDeleted($this->article);
EventHandler::getInstance()->fire($event);
}
diff --git a/wcfsetup/install/files/lib/command/article/UpdateArticle.class.php b/wcfsetup/install/files/lib/command/article/UpdateArticle.class.php
index a7d3b7328c1..7a130f3eb19 100644
--- a/wcfsetup/install/files/lib/command/article/UpdateArticle.class.php
+++ b/wcfsetup/install/files/lib/command/article/UpdateArticle.class.php
@@ -8,6 +8,7 @@
use wcf\data\article\content\ArticleContent;
use wcf\event\article\ArticleUpdated;
use wcf\system\event\EventHandler;
+use wcf\system\moderation\queue\ModerationQueueActivationManager;
use wcf\system\search\SearchIndexManager;
use wcf\system\user\activity\event\UserActivityEventHandler;
use wcf\system\user\notification\object\ArticleUserNotificationObject;
@@ -119,6 +120,13 @@ private function handlePublicationStatusChange(Article $article, int $oldStatus,
[$article->articleID]
);
}
+
+ if ($newStatus !== Article::UNPUBLISHED) {
+ ModerationQueueActivationManager::getInstance()->removeModeratedContent(
+ 'com.woltlab.wcf.article',
+ [$article->articleID]
+ );
+ }
}
private function updateActivityEventAuthor(Article $article): void
diff --git a/wcfsetup/install/files/lib/system/moderation/queue/activation/ArticleModerationQueueActivationHandler.class.php b/wcfsetup/install/files/lib/system/moderation/queue/activation/ArticleModerationQueueActivationHandler.class.php
new file mode 100644
index 00000000000..9d2d32780ab
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/moderation/queue/activation/ArticleModerationQueueActivationHandler.class.php
@@ -0,0 +1,161 @@
+
+ * @since 6.3
+ */
+final class ArticleModerationQueueActivationHandler extends AbstractModerationQueueHandler implements
+ IModerationQueueActivationHandler
+{
+ /**
+ * @inheritDoc
+ */
+ protected $className = Article::class;
+
+ /**
+ * @inheritDoc
+ */
+ protected $definitionName = 'com.woltlab.wcf.moderation.activation';
+
+ /**
+ * @inheritDoc
+ */
+ protected $objectType = 'com.woltlab.wcf.article';
+
+ #[\Override]
+ public function enableContent(ModerationQueue $queue): void
+ {
+ $article = $this->getArticle($queue->objectID);
+ if ($article !== null && $article->publicationStatus === Article::UNPUBLISHED) {
+ new PublishArticle($article)();
+ }
+ }
+
+ #[\Override]
+ public function getDisabledContent(ViewableModerationQueue $queue): string
+ {
+ return WCF::getTPL()->render('wcf', 'moderationArticle', [
+ // @phpstan-ignore property.notFound
+ 'article' => $this->getArticle($queue->getAffectedObject()->articleID),
+ ]);
+ }
+
+ public function getArticle(int $articleID): ?Article
+ {
+ return ArticleRuntimeCache::getInstance()->getObject($articleID);
+ }
+
+ #[\Override]
+ public function assignQueues(array $queues): void
+ {
+ $assignments = $orphanedQueueIDs = [];
+
+ foreach ($queues as $queue) {
+ ArticleRuntimeCache::getInstance()->cacheObjectID($queue->objectID);
+ }
+
+ foreach ($queues as $queue) {
+ $article = ArticleRuntimeCache::getInstance()->getObject($queue->objectID);
+
+ if ($article === null) {
+ $orphanedQueueIDs[] = $queue->queueID;
+ } else {
+ $assignments[$queue->queueID] = $article->canPublish();
+ }
+ }
+
+ ModerationQueueManager::getInstance()->removeOrphans($orphanedQueueIDs);
+ ModerationQueueManager::getInstance()->setAssignment($assignments);
+ }
+
+ #[\Override]
+ public function getContainerID(int $objectID): int
+ {
+ if ($this->isValid($objectID)) {
+ return $this->getArticle($objectID)->getCategory()->categoryID;
+ }
+
+ return 0;
+ }
+
+ #[\Override]
+ public function isValid(int $objectID): bool
+ {
+ return $this->getArticle($objectID) !== null;
+ }
+
+ #[\Override]
+ public function populate(array $queues): void
+ {
+ foreach ($queues as $queue) {
+ ArticleRuntimeCache::getInstance()->cacheObjectID($queue->objectID);
+ }
+
+ foreach ($queues as $object) {
+ $article = ArticleRuntimeCache::getInstance()->getObject($object->objectID);
+ if ($article !== null) {
+ $object->setAffectedObject($article);
+ } else {
+ $object->setIsOrphaned();
+ }
+ }
+ }
+
+ #[\Override]
+ public function canRemoveContent(ModerationQueue $queue): bool
+ {
+ if ($this->isValid($queue->objectID)) {
+ return $this->getArticle($queue->objectID)->canDelete();
+ }
+
+ return false;
+ }
+
+ #[\Override]
+ public function removeContent(ModerationQueue $queue, string $message): void
+ {
+ if ($this->isValid($queue->objectID)) {
+ new SoftDeleteArticle($this->getArticle($queue->objectID))();
+ }
+ }
+
+ #[\Override]
+ public function isAffectedUser(ModerationQueue $queue, int $userID): bool
+ {
+ if (!parent::isAffectedUser($queue, $userID)) {
+ return false;
+ }
+
+ $article = $this->getArticle($queue->objectID);
+ if ($article === null) {
+ return false;
+ }
+
+ /** @see Article::canPublish() */
+ $userProfile = UserProfileRuntimeCache::getInstance()->getObject($userID);
+ if ($userProfile->hasPermission('admin.content.article.canManageArticle')) {
+ return true;
+ }
+
+ return $userProfile->hasPermission('admin.content.article.canManageOwnArticles')
+ && $article->userID === $userProfile->userID;
+ }
+}