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'} + + + + + +
{@$user->getAvatar()->getImageTag($avatarSize)} +
+

+ {if $article->userID} + {$article->username} + {else} + {$article->username} + {/if} + · + {$article->time|plainTime} +

+
+
+ {@$articleContent->getMailText($mimeType)} +
+
+ {/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