From 160f225f130a2cef43283477a55d5d6025411c0c Mon Sep 17 00:00:00 2001
From: =?utf8?q?Joshua=20R=C3=BCsweg?=
Date: Wed, 29 Aug 2018 15:06:11 +0200
Subject: [PATCH] Add article notifications on watched categories See #2642
---
com.woltlab.wcf/objectType.xml | 6 ++
.../templates/email_notification_article.tpl | 36 ++++++++
com.woltlab.wcf/userNotificationEvent.xml | 7 ++
.../lib/data/article/ArticleAction.class.php | 56 +++++++++++-
.../article/content/ArticleContent.class.php | 27 ++++++
.../ArticleUserNotificationEvent.class.php | 91 +++++++++++++++++++
.../ArticleUserNotificationObject.class.php | 43 +++++++++
...rticleUserNotificationObjectType.class.php | 30 ++++++
wcfsetup/install/lang/de.xml | 6 ++
wcfsetup/install/lang/en.xml | 6 ++
10 files changed, 306 insertions(+), 2 deletions(-)
create mode 100644 com.woltlab.wcf/templates/email_notification_article.tpl
create mode 100644 wcfsetup/install/files/lib/system/user/notification/event/ArticleUserNotificationEvent.class.php
create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/ArticleUserNotificationObject.class.php
create mode 100644 wcfsetup/install/files/lib/system/user/notification/object/type/ArticleUserNotificationObjectType.class.php
diff --git a/com.woltlab.wcf/objectType.xml b/com.woltlab.wcf/objectType.xml
index 53b1a60064..20dc196cac 100644
--- a/com.woltlab.wcf/objectType.xml
+++ b/com.woltlab.wcf/objectType.xml
@@ -109,6 +109,12 @@
com.woltlab.wcf.user.objectWatch
wcf\system\user\object\watch\ArticleCategoryUserObjectWatch
+
+ com.woltlab.wcf.article.notification
+ com.woltlab.wcf.notification.objectType
+ wcf\system\user\notification\object\type\ArticleUserNotificationObjectType
+ com.woltlab.wcf.page
+
diff --git a/com.woltlab.wcf/templates/email_notification_article.tpl b/com.woltlab.wcf/templates/email_notification_article.tpl
new file mode 100644
index 0000000000..a514d67238
--- /dev/null
+++ b/com.woltlab.wcf/templates/email_notification_article.tpl
@@ -0,0 +1,36 @@
+{if $mimeType === 'text/plain'}
+{lang}{@$languageVariablePrefix}.mail.plaintext{/lang}
+
+{@$articleContent->getMailText($mimeType)} {* this line ends with a space *}
+{else}
+ {lang}{@$languageVariablePrefix}.mail.html{/lang}
+ {assign var='user' value=$event->getAuthor()}
+ {assign var='article' value=$event->getUserNotificationObject()}
+
+ {if $notificationType == 'instant'}{assign var='avatarSize' value=48}
+ {else}{assign var='avatarSize' value=32}{/if}
+ {capture assign='articleContent'}
+
+ {/capture}
+ {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$articleContent sandbox=true}
+{/if}
diff --git a/com.woltlab.wcf/userNotificationEvent.xml b/com.woltlab.wcf/userNotificationEvent.xml
index c08c4d2fb7..c567c01966 100644
--- a/com.woltlab.wcf/userNotificationEvent.xml
+++ b/com.woltlab.wcf/userNotificationEvent.xml
@@ -115,5 +115,12 @@
wcf\system\user\notification\event\ArticleCommentResponseOwnerUserNotificationEvent
1
+
+
+ article
+ com.woltlab.wcf.article.notification
+ wcf\system\user\notification\event\ArticleUserNotificationEvent
+ 1
+
diff --git a/wcfsetup/install/files/lib/data/article/ArticleAction.class.php b/wcfsetup/install/files/lib/data/article/ArticleAction.class.php
index bd76ed3fef..ac13137ff7 100644
--- a/wcfsetup/install/files/lib/data/article/ArticleAction.class.php
+++ b/wcfsetup/install/files/lib/data/article/ArticleAction.class.php
@@ -16,6 +16,9 @@ use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
use wcf\system\request\LinkHandler;
use wcf\system\search\SearchIndexManager;
use wcf\system\tagging\TagEngine;
+use wcf\system\user\notification\object\ArticleUserNotificationObject;
+use wcf\system\user\notification\UserNotificationHandler;
+use wcf\system\user\object\watch\UserObjectWatchHandler;
use wcf\system\user\storage\UserStorageHandler;
use wcf\system\version\VersionTracker;
use wcf\system\visitTracker\VisitTracker;
@@ -136,10 +139,19 @@ class ArticleAction extends AbstractDatabaseObjectAction {
// reset storage
if (ARTICLE_ENABLE_VISIT_TRACKING) {
UserStorageHandler::getInstance()->resetAll('unreadArticles');
+ UserStorageHandler::getInstance()->resetAll('unreadWatchedArticles');
}
if ($article->publicationStatus == Article::PUBLISHED) {
ArticleEditor::updateArticleCounter([$article->userID => 1]);
+
+ UserObjectWatchHandler::getInstance()->updateObject(
+ 'com.woltlab.wcf.article.category',
+ $article->getCategory()->categoryID,
+ 'article',
+ 'com.woltlab.wcf.article.notification',
+ new ArticleUserNotificationObject($article)
+ );
}
return $article;
@@ -248,7 +260,7 @@ class ArticleAction extends AbstractDatabaseObjectAction {
$publicationStatus = (isset($this->parameters['data']['publicationStatus'])) ? $this->parameters['data']['publicationStatus'] : null;
if ($publicationStatus !== null) {
- $usersToArticles = [];
+ $usersToArticles = $resetNotifications = [];
/** @var ArticleEditor $articleEditor */
foreach ($this->objects as $articleEditor) {
if ($publicationStatus != $articleEditor->publicationStatus) {
@@ -260,9 +272,27 @@ class ArticleAction extends AbstractDatabaseObjectAction {
$usersToArticles[$articleEditor->userID] += ($publicationStatus == Article::PUBLISHED) ? 1 : -1;
}
+
+ if ($publicationStatus == Article::PUBLISHED) {
+ UserObjectWatchHandler::getInstance()->updateObject(
+ 'com.woltlab.wcf.article.category',
+ $articleEditor->getCategory()->categoryID,
+ 'article',
+ 'com.woltlab.wcf.article.notification',
+ new ArticleUserNotificationObject($articleEditor->getDecoratedObject())
+ );
+ }
+ else {
+ $resetNotifications[] = $articleEditor->articleID;
+ }
}
}
+ if (!empty($resetNotifications)) {
+ // delete user notifications
+ UserNotificationHandler::getInstance()->removeNotifications('com.woltlab.wcf.article.notification', $resetNotifications);
+ }
+
if (!empty($usersToArticles)) {
ArticleEditor::updateArticleCounter($usersToArticles);
}
@@ -318,6 +348,8 @@ class ArticleAction extends AbstractDatabaseObjectAction {
TagEngine::getInstance()->deleteObjects('com.woltlab.wcf.article', $articleContentIDs);
// delete entry from search index
SearchIndexManager::getInstance()->delete('com.woltlab.wcf.article', $articleContentIDs);
+ // delete user notifications
+ UserNotificationHandler::getInstance()->removeNotifications('com.woltlab.wcf.article.notification', $articleIDs);
}
$this->unmarkItems();
@@ -505,6 +537,7 @@ class ArticleAction extends AbstractDatabaseObjectAction {
// reset storage
if (WCF::getUser()->userID) {
UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadArticles');
+ UserStorageHandler::getInstance()->reset([WCF::getUser()->userID], 'unreadWatchedArticles');
}
}
@@ -597,10 +630,24 @@ class ArticleAction extends AbstractDatabaseObjectAction {
}
$usersToArticles[$articleEditor->userID]++;
+
+ UserObjectWatchHandler::getInstance()->updateObject(
+ 'com.woltlab.wcf.article.category',
+ $articleEditor->getCategory()->categoryID,
+ 'article',
+ 'com.woltlab.wcf.article.notification',
+ new ArticleUserNotificationObject($articleEditor->getDecoratedObject())
+ );
}
ArticleEditor::updateArticleCounter($usersToArticles);
+ // reset storage
+ if (ARTICLE_ENABLE_VISIT_TRACKING) {
+ UserStorageHandler::getInstance()->resetAll('unreadArticles');
+ UserStorageHandler::getInstance()->resetAll('unreadWatchedArticles');
+ }
+
$this->unmarkItems();
}
@@ -633,7 +680,7 @@ class ArticleAction extends AbstractDatabaseObjectAction {
* Unpublishes articles.
*/
public function unpublish() {
- $usersToArticles = [];
+ $usersToArticles = $articleIDs = [];
foreach ($this->getObjects() as $articleEditor) {
$articleEditor->update(['publicationStatus' => Article::UNPUBLISHED]);
@@ -642,8 +689,13 @@ class ArticleAction extends AbstractDatabaseObjectAction {
}
$usersToArticles[$articleEditor->userID]--;
+
+ $articleIDs[] = $articleEditor->articleID;
}
+ // delete user notifications
+ UserNotificationHandler::getInstance()->removeNotifications('com.woltlab.wcf.article.notification', $articleIDs);
+
ArticleEditor::updateArticleCounter($usersToArticles);
$this->unmarkItems();
diff --git a/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php b/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php
index ca7587f8ca..1fa7b871f0 100644
--- a/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php
+++ b/wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php
@@ -138,6 +138,33 @@ class ArticleContent extends DatabaseObject implements ILinkableObject, IRouteCo
return null;
}
+ /**
+ * Returns a version of this message optimized for use in emails.
+ *
+ * @param string $mimeType Either 'text/plain' or 'text/html'
+ * @return string
+ * @since 3.2
+ */
+ public function getMailText($mimeType = 'text/plain') {
+ switch ($mimeType) {
+ case 'text/plain':
+ $processor = new HtmlOutputProcessor();
+ $processor->setOutputType('text/plain');
+ $processor->process($this->content, 'com.woltlab.wcf.article.content', $this->articleContentID);
+
+ return $processor->getHtml();
+ case 'text/html':
+ // parse and return message
+ $processor = new HtmlOutputProcessor();
+ $processor->setOutputType('text/simplified-html');
+ $processor->process($this->content, 'com.woltlab.wcf.article.content', $this->articleContentID);
+
+ return $processor->getHtml();
+ }
+
+ throw new \LogicException('Unreachable');
+ }
+
/**
* Returns a certain article content or `null` if it does not exist.
*
diff --git a/wcfsetup/install/files/lib/system/user/notification/event/ArticleUserNotificationEvent.class.php b/wcfsetup/install/files/lib/system/user/notification/event/ArticleUserNotificationEvent.class.php
new file mode 100644
index 0000000000..fc294e6bc5
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/user/notification/event/ArticleUserNotificationEvent.class.php
@@ -0,0 +1,91 @@
+
+ * @package WoltLabSuite\Core\System\User\Notification\Object\Type
+ *
+ * @method ArticleUserNotificationObject getUserNotificationObject()
+ */
+class ArticleUserNotificationEvent extends AbstractUserNotificationEvent implements ITestableUserNotificationEvent {
+ use TTestableUserNotificationEvent;
+ use TTestableArticleUserNotificationEvent;
+ use TTestableCategorizedUserNotificationEvent;
+
+ /**
+ * @inheritDoc
+ */
+ public function getTitle() {
+ return $this->getLanguage()->get('wcf.user.notification.article.title');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getMessage() {
+ return $this->getLanguage()->getDynamicVariable('wcf.user.notification.article.message', [
+ 'article' => $this->userNotificationObject,
+ 'author' => $this->author
+ ]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getEmailMessage($notificationType = 'instant') {
+ if ($this->getUserNotificationObject()->isMultilingual) {
+ $articleContent = $this->getUserNotificationObject()->getArticleContents()[$this->getLanguage()->languageID];
+ }
+ else {
+ $articleContent = $this->getUserNotificationObject()->getArticleContents()[0];
+ }
+
+ return [
+ 'message-id' => 'com.woltlab.wcf.article/'.$this->getUserNotificationObject()->articleID,
+ 'template' => 'email_notification_article',
+ 'application' => 'wcf',
+ 'variables' => [
+ 'article' => $this->getUserNotificationObject(),
+ 'articleContent' => $articleContent,
+ 'languageVariablePrefix' => 'wcf.user.notification.article'
+ ]
+ ];
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getLink() {
+ return $this->getUserNotificationObject()->getLink();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function checkAccess() {
+ return $this->getUserNotificationObject()->canRead();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function canBeTriggeredByGuests() {
+ return true;
+ }
+
+ /**
+ * @inheritDoc
+ * @return Article[]
+ */
+ public static function getTestObjects(UserProfile $recipient, UserProfile $author) {
+ return [new ArticleUserNotificationObject(self::getTestArticle(self::createTestCategory(ArticleCategory::OBJECT_TYPE_NAME), $author))];
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/ArticleUserNotificationObject.class.php b/wcfsetup/install/files/lib/system/user/notification/object/ArticleUserNotificationObject.class.php
new file mode 100644
index 0000000000..9f8a69112e
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/user/notification/object/ArticleUserNotificationObject.class.php
@@ -0,0 +1,43 @@
+
+ * @package WoltLabSuite\Core\System\User\Notification\Object
+ *
+ * @method Article getDecoratedObject()
+ * @mixin Article
+ */
+class ArticleUserNotificationObject extends DatabaseObjectDecorator implements IUserNotificationObject {
+ /**
+ * @inheritDoc
+ */
+ protected static $baseClass = Article::class;
+
+ /**
+ * @inheritDoc
+ */
+ public function getTitle() {
+ return $this->getDecoratedObject()->getTitle();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getURL() {
+ return $this->getDecoratedObject()->getLink();
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getAuthorID() {
+ return $this->getDecoratedObject()->userID;
+ }
+}
diff --git a/wcfsetup/install/files/lib/system/user/notification/object/type/ArticleUserNotificationObjectType.class.php b/wcfsetup/install/files/lib/system/user/notification/object/type/ArticleUserNotificationObjectType.class.php
new file mode 100644
index 0000000000..37becbb38c
--- /dev/null
+++ b/wcfsetup/install/files/lib/system/user/notification/object/type/ArticleUserNotificationObjectType.class.php
@@ -0,0 +1,30 @@
+
+ * @package WoltLabSuite\Core\System\User\Notification\Object\Type
+ */
+class ArticleUserNotificationObjectType extends AbstractUserNotificationObjectType {
+ /**
+ * @inheritDoc
+ */
+ protected static $decoratorClassName = ArticleUserNotificationObject::class;
+
+ /**
+ * @inheritDoc
+ */
+ protected static $objectClassName = Article::class;
+
+ /**
+ * @inheritDoc
+ */
+ protected static $objectListClassName = ArticleList::class;
+}
\ No newline at end of file
diff --git a/wcfsetup/install/lang/de.xml b/wcfsetup/install/lang/de.xml
index 9f9d79adfe..987c245ba6 100644
--- a/wcfsetup/install/lang/de.xml
+++ b/wcfsetup/install/lang/de.xml
@@ -4266,6 +4266,12 @@ Benachrichtigungen auf {PAGE_TITLE|language
- getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} und {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} und {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} und{/if} {#$others} weitere Benutzer {if $guestTimesTriggered}und {if $guestTimesTriggered == 1}ein Gast{else}Gäste{/if}{/if}{/if} haben Antworten zum Kommentar von {if $commentAuthor->userID}{$commentAuthor->username}{else}{$commentAuthor->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel {$article->getTitle()} verfasst.]]>
- getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {@$notificationContent[variables][commentAuthor]->username}{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel â{@$notificationContent[variables][article]->getTitle()}â [URL:{link controller='Article' object=$notificationContent[variables][article] isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}] verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]>
- {@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->getAuthor()->userID || $guestTimesTriggered == 0)}hat eine Antwort{else}haben Antworten{/if} zum Kommentar von {if $notificationContent[variables][commentAuthor]->userID}{@$notificationContent[variables][commentAuthor]->username}{else}{@$notificationContent[variables][commentAuthor]->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel {@$notificationContent[variables][article]->getTitle()} verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}
]]>
+
+
+
+ - userID}{@$author->getAnchorTag()}{else}Ein Gast{/if} hat den Artikel {$article->getTitle()} verfasst.]]>
+ - getAuthor()->userID}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{else}Ein Gast{/if} hat den Artikel â{@$notificationContent[variables][articleContent]->getTitle()}â [URL:{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}] verfasst:]]>
+ - {if $event->getAuthor()->userID}{$event->getAuthor()->username}{else}Ein Gast{/if} hat den Artikel {$notificationContent[variables][articleContent]->getTitle()} verfasst:]]>
diff --git a/wcfsetup/install/lang/en.xml b/wcfsetup/install/lang/en.xml
index fbb932d8fd..e52586745d 100644
--- a/wcfsetup/install/lang/en.xml
+++ b/wcfsetup/install/lang/en.xml
@@ -4262,6 +4262,12 @@ your notifications on {PAGE_TITLE|language}
- getAnchorTag()}{if $count != 1}{if $count == 2 && !$guestTimesTriggered} and {else}, {/if}{@$authors[1]->getAnchorTag()}{if $count == 3}{if !$guestTimesTriggered} and {else}, {/if} {@$authors[2]->getAnchorTag()}{/if}{/if}{if $guestTimesTriggered} and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{else}{@$authors[0]->getAnchorTag()}{if $guestTimesTriggered},{else} and{/if} {#$others} other users {if $guestTimesTriggered}and {if $guestTimesTriggered == 1}a guest{else}guests{/if}{/if}{/if} replied to a comment by {if $author->userID}{$author->username}{else}{$author->username}{/if} on your article {$article->getTitle()}.]]>
- username}âs{if $notificationContent[variables][commentAuthor]->userID} [URL:{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}]{/if} comment on your article â{@$article->getTitle()}â [URL:{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}]{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}]]>
- {@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}{$notificationContent[variables][commentAuthor]->username}{else}{$notificationContent[variables][commentAuthor]->username}{/if}âs comment on your article {$article->getTitle()}:]]>
+
+
+
+ - userID}{@$author->getAnchorTag()}{else}A guest{/if} wrote the article {$article->getTitle()}.]]>
+ - getAuthor()->userID}{@$event->getAuthor()->username} [URL:{link controller='User' object=$event->getAuthor() isEmail=true}{/link}]{else}A guest{/if} wrote the article â{@$notificationContent[variables][articleContent]->getTitle()}â [URL:{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}]:]]>
+ - {if $event->getAuthor()->userID}{$event->getAuthor()->username}{else}A guest{/if} wrote the article {$notificationContent[variables][articleContent]->getTitle()}:]]>
--
2.20.1