<title language="en">Articles</title>
<page>com.woltlab.wcf.ArticleList</page>
</item>
-
+ <item identifier="com.woltlab.wcf.WatchedArticleList">
+ <parent>com.woltlab.wcf.ArticleList</parent>
+ <title language="de">Abonnierte Artikel</title>
+ <title language="en">Watched Articles</title>
+ <page>com.woltlab.wcf.WatchedArticleList</page>
+ </item>
<item identifier="com.woltlab.wcf.MembersList">
<menu>com.woltlab.wcf.MainMenu</menu>
<title language="de">Mitglieder</title>
<title>Artikel</title>
</content>
</page>
+ <page identifier="com.woltlab.wcf.WatchedArticleList">
+ <pageType>system</pageType>
+ <controller>wcf\page\WatchedArticleListPage</controller>
+ <handler>wcf\system\page\handler\WatchedArticleListPageHandler</handler>
+ <name language="de">Abonnierte Artikel</name>
+ <name language="en">Watched Articles</name>
+ <options>module_article</options>
+ <parent>com.woltlab.wcf.ArticleList</parent>
+
+ <content language="en">
+ <title>Watched Articles</title>
+ </content>
+ <content language="de">
+ <title>Abonnierte Artikel</title>
+ </content>
+ </page>
<page identifier="com.woltlab.wcf.CategoryArticleList">
<pageType>system</pageType>
<controller>wcf\page\CategoryArticleListPage</controller>
--- /dev/null
+{capture assign='headContent'}
+ {if $pageNo < $pages}
+ <link rel="next" href="{link controller='WatchedArticleList'}pageNo={@$pageNo+1}{/link}">
+ {/if}
+ {if $pageNo > 1}
+ <link rel="prev" href="{link controller='WatchedArticleList'}{if $pageNo > 2}pageNo={@$pageNo-1}{/if}{/link}">
+ {/if}
+{/capture}
+
+{capture assign='headerNavigation'}>
+ {if ARTICLE_ENABLE_VISIT_TRACKING}
+ <li class="jsOnly"><a href="#" title="{lang}wcf.article.markAllAsRead{/lang}" class="markAllAsReadButton jsTooltip"><span class="icon icon16 fa-check"></span> <span class="invisible">{lang}wcf.article.markAllAsRead{/lang}</span></a></li>
+ {/if}
+{/capture}
+
+{capture assign='sidebarRight'}
+ {if !$labelGroups|empty}
+ <form id="sidebarForm" method="post" action="{link application='wcf' controller=$controllerName object=$controllerObject}{/link}">
+ <section class="box">
+ <h2 class="boxTitle">{lang}wcf.label.label{/lang}</h2>
+
+ <div class="boxContent">
+ <dl>
+ {foreach from=$labelGroups item=labelGroup}
+ {if $labelGroup|count}
+ <dt>{$labelGroup->getTitle()}</dt>
+ <dd>
+ <ul class="labelList jsOnly">
+ <li class="dropdown labelChooser" id="labelGroup{@$labelGroup->groupID}" data-group-id="{@$labelGroup->groupID}">
+ <div class="dropdownToggle" data-toggle="labelGroup{@$labelGroup->groupID}"><span class="badge label">{lang}wcf.label.none{/lang}</span></div>
+ <div class="dropdownMenu">
+ <ul class="scrollableDropdownMenu">
+ {foreach from=$labelGroup item=label}
+ <li data-label-id="{@$label->labelID}"><span><span class="badge label{if $label->getClassNames()} {@$label->getClassNames()}{/if}">{lang}{$label->label}{/lang}</span></span></li>
+ {/foreach}
+ </ul>
+ </div>
+ </li>
+ </ul>
+ <noscript>
+ {foreach from=$labelGroups item=labelGroup}
+ <select name="labelIDs[{@$labelGroup->groupID}]">
+ <option value="0">{lang}wcf.label.none{/lang}</option>
+ <option value="-1">{lang}wcf.label.withoutSelection{/lang}</option>
+ {foreach from=$labelGroup item=label}
+ <option value="{@$label->labelID}"{if $labelIDs[$labelGroup->groupID]|isset && $labelIDs[$labelGroup->groupID] == $label->labelID} selected{/if}>{lang}{$label->label}{/lang}</option>
+ {/foreach}
+ </select>
+ {/foreach}
+ </noscript>
+ </dd>
+ {/if}
+ {/foreach}
+ </dl>
+ <div class="formSubmit">
+ <input type="submit" value="{lang}wcf.global.button.submit{/lang}" accesskey="s">
+ </div>
+ </div>
+ </section>
+ </form>
+
+ <script data-relocate="true">
+ $(function() {
+ WCF.Language.addObject({
+ 'wcf.label.none': '{lang}wcf.label.none{/lang}',
+ 'wcf.label.withoutSelection': '{lang}wcf.label.withoutSelection{/lang}'
+ });
+
+ new WCF.Label.Chooser({ {implode from=$labelIDs key=groupID item=labelID}{@$groupID}: {@$labelID}{/implode} }, '#sidebarForm', undefined, true);
+ });
+ </script>
+ {/if}
+{/capture}
+
+{include file='header'}
+
+{hascontent}
+ <div class="paginationTop">
+ {content}
+ {pages print=true assign='pagesLinks' controller='WatchedArticleList' link="pageNo=%d"}
+ {/content}
+ </div>
+{/hascontent}
+
+{if $objects|count}
+ <div class="section">
+ {include file='articleListItems'}
+ </div>
+{else}
+ <p class="info">{lang}wcf.global.noItems{/lang}</p>
+{/if}
+
+<footer class="contentFooter">
+ {hascontent}
+ <div class="paginationBottom">
+ {content}{@$pagesLinks}{/content}
+ </div>
+ {/hascontent}
+
+ {hascontent}
+ <nav class="contentFooterNavigation">
+ <ul>
+ {content}{event name='contentFooterNavigation'}{/content}
+ </ul>
+ </nav>
+ {/hascontent}
+</footer>
+
+{if ARTICLE_ENABLE_VISIT_TRACKING}
+ <script data-relocate="true">
+ require(['WoltLabSuite/Core/Ui/Article/MarkAllAsRead'], function(UiArticleMarkAllAsRead) {
+ UiArticleMarkAllAsRead.init();
+ });
+ </script>
+{/if}
+
+{include file='footer'}
*/
protected static $unreadArticles;
+ /**
+ * number of unread articles in watched categories
+ * @var integer
+ */
+ protected static $unreadWatchedArticles;
+
/**
* list of assigned labels
* @var Label[]
return self::$unreadArticles;
}
+
+ /**
+ * Returns the number of unread articles in watched categories.
+ *
+ * @return integer
+ */
+ public static function getWatchedUnreadArticles() {
+ if (self::$unreadWatchedArticles === null) {
+ self::$unreadWatchedArticles = 0;
+
+ if (WCF::getUser()->userID) {
+ $unreadArticles = UserStorageHandler::getInstance()->getField('unreadWatchedArticles');
+
+ // cache does not exist or is outdated
+ if ($unreadArticles === null) {
+ $categoryIDs = ArticleCategory::getSubscribedCategoryIDs();
+ if (!empty($categoryIDs)) {
+ $conditionBuilder = new PreparedStatementConditionBuilder();
+ $conditionBuilder->add('article.categoryID IN (?)', [$categoryIDs]);
+ $conditionBuilder->add('article.time > ?', [VisitTracker::getInstance()->getVisitTime('com.woltlab.wcf.article')]);
+ $conditionBuilder->add('article.isDeleted = ?', [0]);
+ $conditionBuilder->add('article.publicationStatus = ?', [Article::PUBLISHED]);
+ $conditionBuilder->add('(article.time > tracked_visit.visitTime OR tracked_visit.visitTime IS NULL)');
+
+ $sql = "SELECT COUNT(*)
+ FROM wcf".WCF_N."_article article
+ LEFT JOIN wcf".WCF_N."_tracked_visit tracked_visit
+ ON (tracked_visit.objectTypeID = ".VisitTracker::getInstance()->getObjectTypeID('com.woltlab.wcf.article')."
+ AND tracked_visit.objectID = article.articleID
+ AND tracked_visit.userID = ".WCF::getUser()->userID.")
+ ".$conditionBuilder;
+ $statement = WCF::getDB()->prepareStatement($sql);
+ $statement->execute($conditionBuilder->getParameters());
+ self::$unreadWatchedArticles = $statement->fetchSingleColumn();
+ }
+
+ // update storage unreadEntries
+ UserStorageHandler::getInstance()->update(WCF::getUser()->userID, 'unreadWatchedArticles', self::$unreadWatchedArticles);
+ }
+ else {
+ self::$unreadWatchedArticles = $unreadArticles;
+ }
+ }
+ }
+
+ return self::$unreadWatchedArticles;
+ }
}
--- /dev/null
+<?php
+namespace wcf\page;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\article\CategoryArticleList;
+use wcf\system\exception\IllegalLinkException;
+use wcf\system\request\LinkHandler;
+
+/**
+ * Shows a list of articles in watched categories.
+ *
+ * @author Joshua Ruesweg
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
+ * @since 3.2
+ */
+class WatchedArticleListPage extends ArticleListPage {
+ /**
+ * @inheritDoc
+ */
+ public $loginRequired = true;
+
+ /**
+ * @inheritDoc
+ */
+ public $controllerName = 'WatchedArticleList';
+
+ /**
+ * @inheritDoc
+ */
+ public function readParameters() {
+ parent::readParameters();
+
+ if (empty(ArticleCategory::getSubscribedCategoryIDs())) {
+ throw new IllegalLinkException();
+ }
+
+ $this->canonicalURL = LinkHandler::getInstance()->getLink('WatchedArticleList', $this->controllerParameters, ($this->pageNo > 1 ? 'pageNo=' . $this->pageNo : ''));
+ }
+
+ /**
+ * @inheritDoc
+ */
+ protected function initObjectList() {
+ $this->objectList = new CategoryArticleList(ArticleCategory::getSubscribedCategoryIDs());
+
+ $this->applyFilters();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\page\handler;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\article\ViewableArticle;
+
+/**
+ * Page handler implementation for the page showing the list of articles in watched categories.
+ *
+ * @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\Page\Handler
+ * @since 3.2
+ */
+class WatchedArticleListPageHandler extends AbstractMenuPageHandler {
+ /** @noinspection PhpMissingParentCallCommonInspection */
+ /**
+ * @inheritDoc
+ */
+ public function getOutstandingItemCount(/** @noinspection PhpUnusedParameterInspection */$objectID = null) {
+ return ARTICLE_ENABLE_VISIT_TRACKING ? ViewableArticle::getWatchedUnreadArticles() : 0;
+ }
+
+ /** @noinspection PhpMissingParentCallCommonInspection */
+ /**
+ * @inheritDoc
+ */
+ public function isVisible($objectID = null) {
+ return !empty(ArticleCategory::getSubscribedCategoryIDs());
+ }
+}