Add article notifications on watched categories
authorJoshua Rüsweg <josh@bastelstu.be>
Wed, 29 Aug 2018 13:06:11 +0000 (15:06 +0200)
committerJoshua Rüsweg <josh@bastelstu.be>
Wed, 29 Aug 2018 13:06:11 +0000 (15:06 +0200)
See #2642

com.woltlab.wcf/objectType.xml
com.woltlab.wcf/templates/email_notification_article.tpl [new file with mode: 0644]
com.woltlab.wcf/userNotificationEvent.xml
wcfsetup/install/files/lib/data/article/ArticleAction.class.php
wcfsetup/install/files/lib/data/article/content/ArticleContent.class.php
wcfsetup/install/files/lib/system/user/notification/event/ArticleUserNotificationEvent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/ArticleUserNotificationObject.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/user/notification/object/type/ArticleUserNotificationObjectType.class.php [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 53b1a60064cc5b0fc0b42e45719701eb6d0e64cf..20dc196cac0f3d3b171ba2b47655c67d7070b7b0 100644 (file)
                        <definitionname>com.woltlab.wcf.user.objectWatch</definitionname>
                        <classname>wcf\system\user\object\watch\ArticleCategoryUserObjectWatch</classname>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.article.notification</name>
+                       <definitionname>com.woltlab.wcf.notification.objectType</definitionname>
+                       <classname>wcf\system\user\notification\object\type\ArticleUserNotificationObjectType</classname>
+                       <category>com.woltlab.wcf.page</category>
+               </type>
                <!-- /articles -->
                
                <type>
diff --git a/com.woltlab.wcf/templates/email_notification_article.tpl b/com.woltlab.wcf/templates/email_notification_article.tpl
new file mode 100644 (file)
index 0000000..a514d67
--- /dev/null
@@ -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'}
+       <table cellpadding="0" cellspacing="0" border="0">
+               <tr>
+                       <td><a href="{link controller='User' object=$user isEmail=true}{/link}" title="{$article->username}">{@$user->getAvatar()->getImageTag($avatarSize)}</a></td>
+                       <td class="boxContent">
+                               <div class="containerHeadline">
+                                       <h3>
+                                               {if $article->userID}
+                                                       <a href="{link controller='User' object=$user isEmail=true}{/link}">{$article->username}</a>
+                                               {else}
+                                                       {$article->username}
+                                               {/if}
+                                               &#xb7;
+                                               <a href="{$articleContent->getLink()}"><small>{$article->time|plainTime}</small></a>
+                                       </h3>
+                               </div>
+                               <div>
+                                       {@$articleContent->getMailText($mimeType)}
+                               </div>
+                       </td>
+               </tr>
+       </table>
+       {/capture}
+       {include file='email_paddingHelper' block=true class='box'|concat:$avatarSize content=$articleContent sandbox=true}
+{/if}
index c08c4d2fb7e245ed88dc6b8fed9a6c37487d649c..c567c01966e351098e0e0f3f042e080142eb146d 100644 (file)
                        <classname>wcf\system\user\notification\event\ArticleCommentResponseOwnerUserNotificationEvent</classname>
                        <preset>1</preset>
                </event>
+               
+               <event>
+                       <name>article</name>
+                       <objecttype>com.woltlab.wcf.article.notification</objecttype>
+                       <classname>wcf\system\user\notification\event\ArticleUserNotificationEvent</classname>
+                       <preset>1</preset>
+               </event>
        </import>
 </data>
index bd76ed3fef5207734529c7489ca7e6acc25f53d4..ac13137ff7e1528f926177b8845372ac3a4b29d2 100644 (file)
@@ -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();
index ca7587f8ca221ba606f6462fff228f1ea9079a97..1fa7b871f06495bba7326ac0a48ba1626702259a 100644 (file)
@@ -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 (file)
index 0000000..fc294e6
--- /dev/null
@@ -0,0 +1,91 @@
+<?php
+namespace wcf\system\user\notification\event;
+use wcf\data\article\Article;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\user\UserProfile;
+use wcf\system\user\notification\object\ArticleUserNotificationObject;
+
+/**
+ * Notification event for receiving a user trophy.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..9f8a691
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+namespace wcf\system\user\notification\object;
+use wcf\data\article\Article;
+use wcf\data\DatabaseObjectDecorator;
+
+/**
+ * Represents an article user notification object.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 (file)
index 0000000..37becbb
--- /dev/null
@@ -0,0 +1,30 @@
+<?php
+namespace wcf\system\user\notification\object\type;
+use wcf\data\article\Article;
+use wcf\data\article\ArticleList;
+use wcf\system\user\notification\object\ArticleUserNotificationObject;
+
+/**
+ * Represents a gallery image as a notification object type.
+ *
+ * @author     Joshua Ruesweg
+ * @copyright  2001-2018 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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
index 9f9d79adfe1f34afe820904993fdd5910ee2a218..987c245ba66b1dd94803ca633b09b56da4c974af 100644 (file)
@@ -4266,6 +4266,12 @@ Benachrichtigungen auf <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language
                <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->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}<a href="{link controller='User' object=$commentAuthor}{/link}" class="userLink" data-user-id="{@$commentAuthor->userID}">{$commentAuthor->username}</a>{else}{$commentAuthor->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{$article->getLink()}#comment{@$commentID}/response{@$responseID}">{$article->getTitle()}</a> verfasst.]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} {if $count == 1 && $guestTimesTriggered < 2 && (!$event->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}]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.html"><![CDATA[<p>{@$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}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}">{@$notificationContent[variables][commentAuthor]->username}</a>{else}{@$notificationContent[variables][commentAuthor]->username}{/if} zu {if LANGUAGE_USE_INFORMAL_VARIANT}deinem{else}Ihrem{/if} Artikel <a href="{link controller='Article' object=$notificationContent[variables][article] isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{@$notificationContent[variables][article]->getTitle()}</a> verfasst{if $count == 1 && !$guestTimesTriggered}:{else}.{/if}</p>]]></item>
+               
+               <item name="wcf.user.notification.com.woltlab.wcf.article.notification.article"><![CDATA[Neuer Artikel in abonnierter Kategorie]]></item>
+               <item name="wcf.user.notification.article.title"><![CDATA[Neuer Artikel]]></item>
+               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}Ein Gast{/if} hat den Artikel <a href="{$article->getLink()}">{$article->getTitle()}</a> verfasst.]]></item>
+               <item name="wcf.user.notification.article.mail.plaintext"><![CDATA[{if $event->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:]]></item>
+               <item name="wcf.user.notification.article.mail.html"><![CDATA[<p>{if $event->getAuthor()->userID}<a href="{link controller='User' object=$event->getAuthor() isEmail=true}{/link}">{$event->getAuthor()->username}</a>{else}Ein Gast{/if} hat den Artikel <a href="{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}">{$notificationContent[variables][articleContent]->getTitle()}</a> verfasst:</p>]]></item>
        </category>
        
        <category name="wcf.user.profile">
index fbb932d8fdffdc70e7faf4489a9949295ccc9152..e52586745dae05bba719c3dd5e49e260607c53a0 100644 (file)
@@ -4262,6 +4262,12 @@ your notifications on <a href="{link isEmail=true}{/link}">{PAGE_TITLE|language}
                <item name="wcf.user.notification.articleComment.responseOwner.message.stacked"><![CDATA[{if $count < 4}{@$authors[0]->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}<a href="{link controller='User' object=$author}{/link}" class="userLink" data-user-id="{@$author->userID}">{$author->username}</a>{else}{$author->username}{/if} on your article <a href="{link controller='Article' object=$article}#comments/comment{@$commentID}{/link}">{$article->getTitle()}</a>.]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.plaintext"><![CDATA[{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {@$notificationContent[variables][commentAuthor]->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}]]></item>
                <item name="wcf.user.notification.articleComment.responseOwner.mail.html"><![CDATA[<p>{@$authorList} wrote {if $count == 1 && !$guestTimesTriggered}a reply{else}replies{/if} to {if $notificationContent[variables][commentAuthor]->userID}<a href="{link controller='User' object=$notificationContent[variables][commentAuthor] isEmail=true}{/link}">{$notificationContent[variables][commentAuthor]->username}</a>{else}{$notificationContent[variables][commentAuthor]->username}{/if}’s comment on your article <a href="{link controller='Article' object=$article isEmail=true}#comments/comment{@$commentID}/response{@$responseID}{/link}">{$article->getTitle()}</a>:</p>]]></item>
+               
+               <item name="wcf.user.notification.com.woltlab.wcf.article.notification.article"><![CDATA[Notify me of new articles in watched categories]]></item>
+               <item name="wcf.user.notification.article.title"><![CDATA[New Article]]></item>
+               <item name="wcf.user.notification.article.message"><![CDATA[{if $author->userID}{@$author->getAnchorTag()}{else}A guest{/if} wrote the article <a href="{$article->getLink()}">{$article->getTitle()}</a>.]]></item>
+               <item name="wcf.user.notification.article.mail.plaintext"><![CDATA[{if $event->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}]:]]></item>
+               <item name="wcf.user.notification.article.mail.html"><![CDATA[<p>{if $event->getAuthor()->userID}<a href="{link controller='User' object=$event->getAuthor() isEmail=true}{/link}">{$event->getAuthor()->username}</a>{else}A guest{/if} wrote the article <a href="{link controller='Article' object=$notificationContent[variables][articleContent] isEmail=true}{/link}">{$notificationContent[variables][articleContent]->getTitle()}</a>:</p>]]></item>
        </category>
        
        <category name="wcf.user.profile">