<definitionname>com.woltlab.wcf.label.objectType</definitionname>
<classname>wcf\system\label\object\type\ArticleCategoryLabelObjectTypeHandler</classname>
</type>
+ <type>
+ <name>com.woltlab.wcf.article.comment</name>
+ <definitionname>com.woltlab.wcf.article.discussionProvider</definitionname>
+ <classname>wcf\system\article\discussion\CommentArticleDiscussionProvider</classname>
+ </type>
<!-- /articles -->
<type>
<name>com.woltlab.wcf.sitemap.object</name>
<interfacename>wcf\system\sitemap\object\ISitemapObjectObjectType</interfacename>
</definition>
+
+ <definition>
+ <name>com.woltlab.wcf.article.discussionProvider</name>
+ <interfacename>wcf\system\article\discussion\IArticleDiscussionProvider</interfacename>
+ </definition>
</import>
<delete>
<meta itemprop="dateModified" content="{@$article->time|date:'c'}">
</li>
- {if $article->enableComments}
+ {if $article->getDiscussionProvider()->getDiscussionCountPhrase()}
<li itemprop="interactionStatistic" itemscope itemtype="http://schema.org/InteractionCounter">
<span class="icon icon16 fa-comments"></span>
- <span>{lang}wcf.article.articleComments{/lang}</span>
+ <span>{$article->getDiscussionProvider()->getDiscussionCountPhrase()}</span>
<meta itemprop="interactionType" content="http://schema.org/CommentAction">
- <meta itemprop="userInteractionCount" content="{@$article->comments}">
+ <meta itemprop="userInteractionCount" content="{@$article->getDiscussionProvider()->getDiscussionCount()}">
</li>
{/if}
{event name='beforeComments'}
-{if $article->enableComments}
- {if $commentList|count || $commentCanAdd}
- <section id="comments" class="section sectionContainerList">
- <h2 class="sectionTitle">{lang}wcf.global.comments{/lang}{if $article->comments} <span class="badge">{#$article->comments}</span>{/if}</h2>
-
- {include file='__commentJavaScript' commentContainerID='articleCommentList'}
-
- <ul id="articleCommentList" class="commentList containerList" data-can-add="{if $commentCanAdd}true{else}false{/if}" data-object-id="{@$articleContentID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
- {if $commentCanAdd}{include file='commentListAddComment' wysiwygSelector='articleCommentListAddComment'}{/if}
- {include file='commentList'}
- </ul>
- </section>
- {/if}
-{/if}
+{@$article->getDiscussionProvider()->renderDiscussions()}
{if MODULE_LIKE && ARTICLE_ENABLE_LIKE}
<script data-relocate="true">
--- /dev/null
+{if $commentList|count || $commentCanAdd}
+ <section id="comments" class="section sectionContainerList">
+ <h2 class="sectionTitle">{lang}wcf.global.comments{/lang}{if $article->comments} <span class="badge">{#$article->comments}</span>{/if}</h2>
+
+ {include file='__commentJavaScript' commentContainerID='articleCommentList'}
+
+ <ul id="articleCommentList" class="commentList containerList" data-can-add="{if $commentCanAdd}true{else}false{/if}" data-object-id="{@$articleContentID}" data-object-type-id="{@$commentObjectTypeID}" data-comments="{@$commentList->countObjects()}" data-last-comment-time="{@$lastCommentTime}">
+ {if $commentCanAdd}{include file='commentListAddComment' wysiwygSelector='articleCommentListAddComment'}{/if}
+ {include file='commentList'}
+ </ul>
+ </section>
+{/if}
{@$article->time|time}
</li>
- {if $article->enableComments}
+ {if $article->getDiscussionProvider()->getDiscussionCountPhrase()}
<li>
<span class="icon icon16 fa-comments"></span>
- {lang}wcf.article.articleComments{/lang}
+ {$article->getDiscussionProvider()->getDiscussionCountPhrase()}
</li>
{/if}
</a>
</li>
{/foreach}
-</ul>
\ No newline at end of file
+</ul>
{elseif $boxSortField == 'views'}
{lang article=$boxArticle}wcf.article.articleViews{/lang}
{elseif $boxSortField == 'comments'}
- {lang article=$boxArticle}wcf.article.articleComments{/lang}
+ {$boxArticle->getDiscussionProvider()->getDiscussionCountPhrase()}
{elseif $boxSortField == 'cumulativeLikes'}
{if MODULE_LIKE && $__wcf->getSession()->getPermission('user.like.canViewLike') && ($boxArticle->likes || $boxArticle->dislikes)}
<span class="wcfLikeCounter{if $boxArticle->cumulativeLikes > 0} likeCounterLiked{elseif $boxArticle->cumulativeLikes < 0}likeCounterDisliked{/if}">
<li>
<span class="icon icon16 fa-comments"></span>
- {lang article=$boxArticle}wcf.article.articleComments{/lang}
+ {$boxArticle->getDiscussionProvider()->getDiscussionCountPhrase()}
</li>
</ul>
</a>
use wcf\data\article\content\ArticleContent;
use wcf\data\DatabaseObject;
use wcf\data\ILinkableObject;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\system\article\discussion\CommentArticleDiscussionProvider;
+use wcf\system\article\discussion\IArticleDiscussionProvider;
+use wcf\system\article\discussion\VoidArticleDiscussionProvider;
use wcf\system\WCF;
/**
*/
protected $category;
+ /**
+ * @var IArticleDiscussionProvider
+ * @since 3.2
+ */
+ protected $discussionProvider;
+
/**
* Returns true if the active user can delete this article.
*
return $this->category;
}
+
+ /**
+ * Sets the discussion provider for this article.
+ *
+ * @param IArticleDiscussionProvider $discussionProvider
+ * @since 3.2
+ */
+ public function setDiscussionProvider(IArticleDiscussionProvider $discussionProvider) {
+ $this->discussionProvider = $discussionProvider;
+ }
+
+ /**
+ * Returns the responsible discussion provider for this article.
+ *
+ * @return IArticleDiscussionProvider
+ * @since 3.2
+ */
+ public function getDiscussionProvider() {
+ if ($this->discussionProvider === null) {
+ foreach (self::getAllDiscussionProviders() as $discussionProvider) {
+ if (call_user_func([$discussionProvider, 'isResponsible'], $this)) {
+ $this->setDiscussionProvider(new $discussionProvider($this));
+ break;
+ }
+ }
+
+ if ($this->discussionProvider === null) {
+ throw new \RuntimeException('No discussion provider has claimed to be responsible for the article #' . $this->articleID);
+ }
+ }
+
+ return $this->discussionProvider;
+ }
+
+ /**
+ * Returns the list of the available discussion providers.
+ *
+ * @return string[]
+ * @since 3.2
+ */
+ public static function getAllDiscussionProviders(): array {
+ /** @var string[] $discussionProviders */
+ static $discussionProviders;
+
+ if ($discussionProviders === null) {
+ $discussionProviders = [];
+
+ $objectTypes = ObjectTypeCache::getInstance()->getObjectTypes('com.woltlab.wcf.article.discussionProvider');
+ $commentProvider = '';
+ foreach ($objectTypes as $objectType) {
+ // the comment and the "void" provider should always be the last in the list
+ if ($objectType->className === CommentArticleDiscussionProvider::class) {
+ $commentProvider = $objectType->className;
+ continue;
+ }
+
+ $discussionProviders[] = $objectType->className;
+ }
+
+ $discussionProviders[] = $commentProvider;
+ $discussionProviders[] = VoidArticleDiscussionProvider::class;
+ }
+
+ return $discussionProviders;
+ }
}
$contentList->getConditionBuilder()->add('(article_content.languageID IS NULL OR article_content.languageID = ?)', [WCF::getLanguage()->languageID]);
$contentList->readObjects();
foreach ($contentList as $articleContent) {
- $this->objects[$articleContent->articleID]->setArticleContent($articleContent);
+ $article = $this->objects[$articleContent->articleID];
+ $article->setArticleContent($articleContent);
+
+ // Some providers do pre-populate internal caches in order to retrieve the data
+ // for many objects in a single step.
+ $article->getDiscussionProvider();
}
}
}
$this->article = ViewableArticle::getArticle($this->articleContent->articleID, false);
+ $this->article->getDiscussionProvider()->setArticleContent($this->articleContent->getDecoratedObject());
$this->category = $this->article->getCategory();
// update interface language
WCF::getTPL()->assign([
'previousArticle' => $this->previousArticle,
'nextArticle' => $this->nextArticle,
- 'commentCanAdd' => WCF::getSession()->getPermission('user.article.canAddComment'),
- 'commentList' => $this->commentList,
- 'commentObjectTypeID' => $this->commentObjectTypeID,
- 'lastCommentTime' => $this->commentList ? $this->commentList->getMinCommentTime() : 0,
- 'likeData' => (MODULE_LIKE && $this->commentList) ? $this->commentList->getLikeData() : [],
- 'articleLikeData' => $this->articleLikeData
+ 'articleLikeData' => $this->articleLikeData,
+
+ // nullified values for backwards-compatibility
+ 'commentCanAdd' => 0,
+ 'commentList' => null,
+ 'commentObjectTypeID' => 0,
+ 'lastCommentTime' => 0,
+ 'likeData' => [],
]);
}
}
--- /dev/null
+<?php
+declare(strict_types=1);
+namespace wcf\system\article\discussion;
+use wcf\data\article\Article;
+use wcf\data\article\content\ArticleContent;
+
+/**
+ * Default implementation for discussion provider for articles. Any actual implementation
+ * should derive from this class for forwards-compatibility.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Article\Discussion
+ * @since 3.2
+ */
+abstract class AbstractArticleDiscussionProvider implements IArticleDiscussionProvider {
+ /**
+ * @var Article
+ */
+ protected $article;
+
+ /**
+ * @var ArticleContent
+ */
+ protected $articleContent;
+
+ /**
+ * AbstractArticleDiscussionProvider constructor.
+ *
+ * @param Article $article
+ */
+ public function __construct(Article $article) {
+ $this->article = $article;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function setArticleContent(ArticleContent $articleContent) {
+ $this->articleContent = $articleContent;
+ }
+}
--- /dev/null
+<?php
+declare(strict_types=1);
+namespace wcf\system\article\discussion;
+use wcf\data\article\Article;
+use wcf\system\comment\CommentHandler;
+use wcf\system\WCF;
+
+/**
+ * The built-in discussion provider using the native comment system.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Article\Discussion
+ * @since 3.2
+ */
+class CommentArticleDiscussionProvider extends AbstractArticleDiscussionProvider {
+ /**
+ * @inheritDoc
+ */
+ public function getDiscussionCount(): int {
+ return $this->article->comments;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getDiscussionCountPhrase(): string {
+ return WCF::getLanguage()->getDynamicVariable('wcf.article.articleComments', ['article' => $this->article]);
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function renderDiscussions(): string {
+ $commentCanAdd = WCF::getSession()->getPermission('user.article.canAddComment');
+ $commentObjectTypeID = CommentHandler::getInstance()->getObjectTypeID('com.woltlab.wcf.articleComment');
+ $commentManager = CommentHandler::getInstance()->getObjectType($commentObjectTypeID)->getProcessor();
+ $commentList = CommentHandler::getInstance()->getCommentList($commentManager, $commentObjectTypeID, $this->articleContent->articleContentID);
+
+ WCF::getTPL()->assign([
+ 'commentCanAdd' => $commentCanAdd,
+ 'commentList' => $commentList,
+ 'commentObjectTypeID' => $commentObjectTypeID,
+ 'lastCommentTime' => $commentList->getMinCommentTime(),
+ 'likeData' => (MODULE_LIKE) ? $commentList->getLikeData() : [],
+ ]);
+
+ return WCF::getTPL()->fetch('articleComments');
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function isResponsible(Article $article): bool {
+ return !!$article->enableComments;
+ }
+}
--- /dev/null
+<?php
+declare(strict_types=1);
+namespace wcf\system\article\discussion;
+use wcf\data\article\Article;
+use wcf\data\article\content\ArticleContent;
+
+/**
+ * Discussion provider for articles.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Article\Discussion
+ * @since 3.2
+ */
+interface IArticleDiscussionProvider {
+ /**
+ * Returns the number of discussion items.
+ *
+ * @return int
+ */
+ public function getDiscussionCount(): int;
+
+ /**
+ * Returns the simple phrase "X <discussions>" that is used for both the statistics
+ * and the meta data in the article's headline.
+ *
+ * @return string
+ */
+ public function getDiscussionCountPhrase(): string;
+
+ /**
+ * Renders the input and display section of the associated discussion.
+ *
+ * @return string
+ */
+ public function renderDiscussions(): string;
+
+ /**
+ * Sets the content object required for the separate discussions per article language.
+ *
+ * @param ArticleContent $articleContent
+ */
+ public function setArticleContent(ArticleContent $articleContent);
+
+ /**
+ * Returning true will assign this provider to the article, otherwise the next
+ * possible provider is being evaluated.
+ *
+ * @param Article $article
+ * @return bool
+ */
+ public static function isResponsible(Article $article): bool;
+}
--- /dev/null
+<?php
+declare(strict_types=1);
+namespace wcf\system\article\discussion;
+use wcf\data\article\Article;
+
+/**
+ * Represents a non-existing discussion provider and is used when there is no other
+ * type of discussion being available. This provider is always being evaluated last.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2018 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\System\Article\Discussion
+ * @since 3.2
+ */
+class VoidArticleDiscussionProvider extends AbstractArticleDiscussionProvider {
+ /**
+ * @inheritDoc
+ */
+ public function getDiscussionCount(): int {
+ return 0;
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function getDiscussionCountPhrase(): string {
+ return '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public function renderDiscussions(): string {
+ return '';
+ }
+
+ /**
+ * @inheritDoc
+ */
+ public static function isResponsible(Article $article): bool {
+ return true;
+ }
+}