Added article search support
authorMarcel Werk <burntime@woltlab.com>
Tue, 7 Jun 2016 11:16:10 +0000 (13:16 +0200)
committerMarcel Werk <burntime@woltlab.com>
Tue, 7 Jun 2016 15:52:35 +0000 (17:52 +0200)
com.woltlab.wcf/objectType.xml
com.woltlab.wcf/templates/searchArticle.tpl [new file with mode: 0644]
wcfsetup/install/files/lib/data/article/ArticleAction.class.php
wcfsetup/install/files/lib/data/article/content/SearchResultArticleContent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/article/content/SearchResultArticleContentList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/article/content/ViewableArticleContent.class.php
wcfsetup/install/files/lib/data/article/content/ViewableArticleContentList.class.php
wcfsetup/install/files/lib/system/search/ArticleSearch.class.php [new file with mode: 0644]

index f528bd2335f82e2b330edf9c3ea37d7972cca30c..6bd115b3dbf370a9ffe3d427911d5454c75506b6 100644 (file)
                        <definitionname>com.woltlab.wcf.like.likeableObject</definitionname>
                        <classname>wcf\data\article\LikeableArticleProvider</classname>
                </type>
-               
+               <type>
+                       <name>com.woltlab.wcf.article</name>
+                       <definitionname>com.woltlab.wcf.searchableObjectType</definitionname>
+                       <classname>wcf\system\search\ArticleSearch</classname>
+                       <searchindex>wcf1_article_search_index</searchindex>
+               </type>
                <!-- /articles -->
                
                <type>
diff --git a/com.woltlab.wcf/templates/searchArticle.tpl b/com.woltlab.wcf/templates/searchArticle.tpl
new file mode 100644 (file)
index 0000000..5955774
--- /dev/null
@@ -0,0 +1,14 @@
+<dl>
+       <dt><label>{lang}wcf.article.search.categories{/lang}</label></dt>
+       <dd>
+               <ul class="scrollableCheckboxList">
+                       {foreach from=$articleCategoryList item=category}
+                               <li{if $category->getDepth() > 1} style="padding-left: {$category->getDepth()*20-20}px"{/if}>
+                                       <label><input type="checkbox" name="articleCategoryIDs[]" value="{@$category->categoryID}"{if $category->categoryID|in_array:$articleCategoryIDs} checked="checked"{/if} /> {$category->getTitle()}</label>
+                               </li>
+                       {/foreach}
+               </ul>
+       </dd>
+</dl>
+
+{event name='fields'}
index 7633bfe1af16dc7950576efa8530e7610d75a647..8fc73b8bccffb5c4b32c99c00a51f676f64c3ed4 100644 (file)
@@ -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 (file)
index 0000000..7af73b3
--- /dev/null
@@ -0,0 +1,90 @@
+<?php
+namespace wcf\data\article\content;;
+use wcf\data\search\ISearchResultObject;
+use wcf\system\request\LinkHandler;
+use wcf\system\search\SearchResultTextParser;
+
+/**
+ * Represents an article content as a search result.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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 '<div class="box96">'.$this->getImage()->getElementTag(96).'<div>'.$message.'</div></div>';
+               }
+               
+               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 (file)
index 0000000..a118967
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+namespace wcf\data\article\content;
+
+/**
+ * Represents a list of article content as search results.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+}
index eba5e2b025346b0b36c6ab1e661ccf8d3d2664c0..6b0f00eee8dd1a3fee1606dda941464aeb90f043 100644 (file)
@@ -1,5 +1,6 @@
 <?php
 namespace wcf\data\article\content;
+use wcf\data\article\ViewableArticle;
 use wcf\data\media\ViewableMedia;
 use wcf\data\DatabaseObjectDecorator;
 
@@ -10,7 +11,7 @@ use wcf\data\DatabaseObjectDecorator;
  * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @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.
         *
index 278e9db2ed1eb456fc058204827480deee9e5bbe..ffa29ee47579a354b29bb7a157cf6501f431e586 100644 (file)
@@ -9,7 +9,7 @@ use wcf\data\media\ViewableMediaList;
  * @copyright  2001-2016 WoltLab GmbH
  * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
  * @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 (file)
index 0000000..f3af3bb
--- /dev/null
@@ -0,0 +1,178 @@
+<?php
+namespace wcf\system\search;
+use wcf\data\article\category\ArticleCategory;
+use wcf\data\article\content\SearchResultArticleContent;
+use wcf\data\article\content\SearchResultArticleContentList;
+use wcf\data\category\CategoryNodeTree;
+use wcf\form\IForm;
+use wcf\form\SearchForm;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\exception\PermissionDeniedException;
+use wcf\system\page\PageLocationManager;
+use wcf\system\WCF;
+use wcf\util\ArrayUtil;
+
+/**
+ * An implementation of ISearchableObjectType for searching in articles.
+ *
+ * @author      Marcel Werk
+ * @copyright  2001-2016 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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');
+       }
+}