From 2fd812d7a7b36979ee15e20c86a2bf5ed44964a6 Mon Sep 17 00:00:00 2001 From: Marcel Werk Date: Tue, 7 Jun 2016 13:16:10 +0200 Subject: [PATCH] Added article search support --- com.woltlab.wcf/objectType.xml | 7 +- com.woltlab.wcf/templates/searchArticle.tpl | 14 ++ .../lib/data/article/ArticleAction.class.php | 37 ++++ .../SearchResultArticleContent.class.php | 90 +++++++++ .../SearchResultArticleContentList.class.php | 24 +++ .../content/ViewableArticleContent.class.php | 31 ++- .../ViewableArticleContentList.class.php | 2 +- .../lib/system/search/ArticleSearch.class.php | 178 ++++++++++++++++++ 8 files changed, 380 insertions(+), 3 deletions(-) create mode 100644 com.woltlab.wcf/templates/searchArticle.tpl create mode 100644 wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php create mode 100644 wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php create mode 100644 wcfsetup/install/files/lib/system/search/ArticleSearch.class.php diff --git a/com.woltlab.wcf/objectType.xml b/com.woltlab.wcf/objectType.xml index f528bd2335..6bd115b3db 100644 --- a/com.woltlab.wcf/objectType.xml +++ b/com.woltlab.wcf/objectType.xml @@ -39,7 +39,12 @@ com.woltlab.wcf.like.likeableObject wcf\data\article\LikeableArticleProvider - + + com.woltlab.wcf.article + com.woltlab.wcf.searchableObjectType + wcf\system\search\ArticleSearch + wcf1_article_search_index + diff --git a/com.woltlab.wcf/templates/searchArticle.tpl b/com.woltlab.wcf/templates/searchArticle.tpl new file mode 100644 index 0000000000..59557743c7 --- /dev/null +++ b/com.woltlab.wcf/templates/searchArticle.tpl @@ -0,0 +1,14 @@ +
+
+
+
    + {foreach from=$articleCategoryList item=category} + getDepth() > 1} style="padding-left: {$category->getDepth()*20-20}px"{/if}> + + + {/foreach} +
+
+
+ +{event name='fields'} diff --git a/wcfsetup/install/files/lib/data/article/ArticleAction.class.php b/wcfsetup/install/files/lib/data/article/ArticleAction.class.php index 7633bfe1af..8fc73b8bcc 100644 --- a/wcfsetup/install/files/lib/data/article/ArticleAction.class.php +++ b/wcfsetup/install/files/lib/data/article/ArticleAction.class.php @@ -3,7 +3,10 @@ namespace wcf\data\article; use wcf\data\article\content\ArticleContent; use wcf\data\article\content\ArticleContentEditor; use wcf\data\AbstractDatabaseObjectAction; +use wcf\system\comment\CommentHandler; use wcf\system\language\LanguageFactory; +use wcf\system\like\LikeHandler; +use wcf\system\search\SearchIndexManager; use wcf\system\tagging\TagEngine; /** @@ -57,6 +60,7 @@ class ArticleAction extends AbstractDatabaseObjectAction { // save article content if (!empty($this->parameters['content'])) { foreach ($this->parameters['content'] as $languageID => $content) { + /** @var ArticleContent $articleContent */ $articleContent = ArticleContentEditor::create([ 'articleID' => $article->articleID, 'languageID' => ($languageID ?: null), @@ -70,6 +74,9 @@ class ArticleAction extends AbstractDatabaseObjectAction { if (!empty($content['tags'])) { TagEngine::getInstance()->addObjectTags('com.woltlab.wcf.article', $articleContent->articleContentID, $content['tags'], ($languageID ?: LanguageFactory::getInstance()->getDefaultLanguageID())); } + + // update search index + SearchIndexManager::getInstance()->add('com.woltlab.wcf.article', $articleContent->articleContentID, $articleContent->content, $articleContent->title, $article->time, $article->userID, $article->username, ($languageID ?: null), $articleContent->teaser); } } @@ -118,8 +125,38 @@ class ArticleAction extends AbstractDatabaseObjectAction { if (!empty($content['tags'])) { TagEngine::getInstance()->addObjectTags('com.woltlab.wcf.article', $articleContent->articleContentID, $content['tags'], ($languageID ?: LanguageFactory::getInstance()->getDefaultLanguageID())); } + + // update search index + SearchIndexManager::getInstance()->add('com.woltlab.wcf.article', $articleContent->articleContentID, $articleContent->content, $articleContent->title, $article->time, $article->userID, $article->username, ($languageID ?: null), $articleContent->teaser); } } } } + + /** + * @inheritDoc + */ + public function delete() { + $articleIDs = $articleContentIDs = []; + foreach ($this->getObjects() as $article) { + $articleIDs[] = $article->articleID; + foreach ($article->getArticleContent() as $articleContent) { + $articleContentIDs[] = $articleContent->articleContentID; + } + } + + // delete articles + parent::delete(); + + if (!empty($articleIDs)) { + // delete like data + LikeHandler::getInstance()->removeLikes('com.woltlab.wcf.likeableArticle', $articleIDs); + // delete comments + CommentHandler::getInstance()->deleteObjects('com.woltlab.wcf.article', $articleContentIDs); + // delete tag to object entries + TagEngine::getInstance()->deleteObjects('com.woltlab.wcf.article', $articleContentIDs); + // delete entry from search index + SearchIndexManager::getInstance()->delete('com.woltlab.wcf.article', $articleContentIDs); + } + } } diff --git a/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php b/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php new file mode 100644 index 0000000000..7af73b3685 --- /dev/null +++ b/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php @@ -0,0 +1,90 @@ + + * @package com.woltlab.wcf + * @subpackage data.article.content + * @category Community Framework + * @since 2.2 + */ +class SearchResultArticleContent extends ViewableArticleContent implements ISearchResultObject { + /** + * @inheritDoc + */ + public function getUserProfile() { + return $this->getArticle()->getUserProfile(); + } + + /** + * @inheritDoc + */ + public function getSubject() { + return $this->getDecoratedObject()->getTitle(); + } + + /** + * @inheritDoc + */ + public function getTime() { + return $this->getArticle()->time; + } + + /** + * @inheritDoc + */ + public function getLink($query = '') { + $parameters = [ + 'object' => $this->getDecoratedObject(), + 'forceFrontend' => true + ]; + + if ($query) { + $parameters['highlight'] = urlencode($query); + } + + return LinkHandler::getInstance()->getLink('Article', $parameters); + } + + /** + * @inheritDoc + */ + public function getObjectTypeName() { + return 'com.woltlab.wcf.article'; + } + + /** + * @inheritDoc + */ + public function getFormattedMessage() { + // @todo + $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getFormattedContent()); + + if ($this->getImage()) { + return '
'.$this->getImage()->getElementTag(96).'
'.$message.'
'; + } + + return $message; + } + + /** + * @inheritDoc + */ + public function getContainerTitle() { + return ''; + } + + /** + * @inheritDoc + */ + public function getContainerLink() { + return ''; + } +} diff --git a/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php b/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php new file mode 100644 index 0000000000..a118967351 --- /dev/null +++ b/wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php @@ -0,0 +1,24 @@ + + * @package com.woltlab.wcf + * @subpackage data.article.content + * @category Community Framework + * @since 2.2 + * + * @method SearchResultEntry current() + * @method SearchResultEntry[] getObjects() + * @method SearchResultEntry|null search($objectID) + */ +class SearchResultArticleContentList extends ViewableArticleContentList { + /** + * @inheritDoc + */ + public $decoratorClassName = SearchResultArticleContent::class; +} diff --git a/wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php b/wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php index eba5e2b025..6b0f00eee8 100644 --- a/wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php +++ b/wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php @@ -1,5 +1,6 @@ * @package com.woltlab.wcf - * @subpackage data.article + * @subpackage data.article.content * @category Community Framework * @since 2.2 * @@ -29,6 +30,12 @@ class ViewableArticleContent extends DatabaseObjectDecorator { */ protected $image; + /** + * article object + * @var ViewableArticle + */ + protected $article; + /** * Gets a specific article content decorated as viewable article content. * @@ -44,6 +51,28 @@ class ViewableArticleContent extends DatabaseObjectDecorator { return null; } + /** + * Returns article object. + * + * @return ViewableArticle + */ + public function getArticle() { + if ($this->article === null) { + $this->article = new ViewableArticle($this->getDecoratedObject()->getArticle()); + } + + return $this->article; + } + + /** + * Sets the article objects. + * + * @param ViewableArticle $article + */ + public function setArticle(ViewableArticle $article) { + $this->article = $article; + } + /** * Returns the article's image. * diff --git a/wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php b/wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php index 278e9db2ed..ffa29ee475 100644 --- a/wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php +++ b/wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php @@ -9,7 +9,7 @@ use wcf\data\media\ViewableMediaList; * @copyright 2001-2016 WoltLab GmbH * @license GNU Lesser General Public License * @package com.woltlab.wcf - * @subpackage data.article + * @subpackage data.article.content * @category Community Framework * @since 2.2 */ diff --git a/wcfsetup/install/files/lib/system/search/ArticleSearch.class.php b/wcfsetup/install/files/lib/system/search/ArticleSearch.class.php new file mode 100644 index 0000000000..f3af3bb5bc --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/ArticleSearch.class.php @@ -0,0 +1,178 @@ + + * @package com.woltlab.wcf + * @subpackage system.search + * @category Community Framework + * @since 2.2 + */ +class ArticleSearch extends AbstractSearchableObjectType { + /** + * ids of the selected categories + * @var integer[] + */ + public $articleCategoryIDs = []; + + /** + * message data cache + * @var SearchResultArticleContent[] + */ + public $messageCache = []; + + /** + * @inheritDoc + */ + public function cacheObjects(array $objectIDs, array $additionalData = null) { + $list = new SearchResultArticleContentList(); + $list->setObjectIDs($objectIDs); + $list->readObjects(); + foreach ($list->getObjects() as $content) { + $this->messageCache[$content->articleContentID] = $content; + } + } + + /** + * @inheritDoc + */ + public function getObject($objectID) { + if (isset($this->messageCache[$objectID])) return $this->messageCache[$objectID]; + return null; + } + + /** + * @inheritDoc + */ + public function getTableName() { + return 'wcf'.WCF_N.'_article_content'; + } + + /** + * @inheritDoc + */ + public function getIDFieldName() { + return $this->getTableName().'.articleContentID'; + } + + /** + * @inheritDoc + */ + public function getSubjectFieldName() { + return $this->getTableName().'.title'; + } + + /** + * @inheritDoc + */ + public function getUsernameFieldName() { + return 'wcf'.WCF_N.'_article.username'; + } + + /** + * @inheritDoc + */ + public function getTimeFieldName() { + return 'wcf'.WCF_N.'_article.time'; + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @inheritDoc + */ + public function getConditions(IForm $form = null) { + // accessible category ids + if (isset($_POST['articleCategoryIDs'])) $this->articleCategoryIDs = ArrayUtil::toIntegerArray($_POST['articleCategoryIDs']); + $accessibleCategoryIDs = ArticleCategory::getAccessibleCategoryIDs(); + if (!empty($this->articleCategoryIDs)) { + $this->articleCategoryIDs = array_intersect($accessibleCategoryIDs, $this->articleCategoryIDs); + } + else { + $this->articleCategoryIDs = $accessibleCategoryIDs; + } + if (empty($this->articleCategoryIDs)) { + throw new PermissionDeniedException(); + } + + $conditionBuilder = new PreparedStatementConditionBuilder(); + $conditionBuilder->add('wcf'.WCF_N.'_article.categoryID IN (?) AND wcf'.WCF_N.'_article.publicationStatus = ?', [$this->articleCategoryIDs, 1]); + + return $conditionBuilder; + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @inheritDoc + */ + public function getJoins() { + return 'JOIN wcf'.WCF_N.'_article ON (wcf'.WCF_N.'_article.articleID = '.$this->getTableName().'.articleID)'; + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @inheritDoc + */ + public function getFormTemplateName() { + return 'searchArticle'; + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @inheritDoc + */ + public function getAdditionalData() { + return ['articleCategoryIDs' => $this->articleCategoryIDs]; + } + + /** @noinspection PhpMissingParentCallCommonInspection */ + /** + * @inheritDoc + */ + public function isAccessible() { + return MODULE_ARTICLE; + } + + /** + * @inheritDoc + */ + public function show(IForm $form = null) { + /** @var SearchForm $form */ + + $categoryTree = new CategoryNodeTree('com.woltlab.wcf.article.category'); + $categoryList = $categoryTree->getIterator(); + $categoryList->setMaxDepth(0); + + // get existing values + if ($form !== null && isset($form->searchData['additionalData']['com.woltlab.wcf.article'])) { + $this->articleCategoryIDs = $form->searchData['additionalData']['com.woltlab.wcf.article']['articleCategoryIDs']; + } + + WCF::getTPL()->assign([ + 'articleCategoryIDs' => $this->articleCategoryIDs, + 'articleCategoryList' => $categoryList + ]); + } + + /** + * @inheritDoc + * @since 2.2 + */ + public function setLocation() { + PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.ArticleList'); + } +} -- 2.20.1