Added CMS pages to the search index
authorMarcel Werk <burntime@woltlab.com>
Tue, 21 Mar 2017 13:29:25 +0000 (14:29 +0100)
committerMarcel Werk <burntime@woltlab.com>
Tue, 21 Mar 2017 13:29:32 +0000 (14:29 +0100)
Closes #2203

com.woltlab.wcf/objectType.xml
wcfsetup/install/files/lib/data/page/PageAction.class.php
wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/package/plugin/PagePackageInstallationPlugin.class.php
wcfsetup/install/files/lib/system/search/PageSearch.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php [new file with mode: 0644]
wcfsetup/install/lang/de.xml
wcfsetup/install/lang/en.xml

index 7d74a8cd2fdcaae2609229f1380a7751f11581e0..445532be52b19f1f30333107947bfea1f891454c 100644 (file)
                        <definitionname>com.woltlab.wcf.message</definitionname>
                </type>
                
+               <type>
+                       <name>com.woltlab.wcf.page</name>
+                       <definitionname>com.woltlab.wcf.searchableObjectType</definitionname>
+                       <classname>wcf\system\search\PageSearch</classname>
+                       <searchindex>wcf1_page_search_index</searchindex>
+               </type>
+               
                <type>
                        <name>com.woltlab.wcf.bbcode.smiley</name>
                        <definitionname>com.woltlab.wcf.category</definitionname>
                        <classname>wcf\system\worker\ArticleRebuildDataWorker</classname>
                        <nicevalue>50</nicevalue>
                </type>
+               <type>
+                       <name>com.woltlab.wcf.page</name>
+                       <definitionname>com.woltlab.wcf.rebuildData</definitionname>
+                       <classname>wcf\system\worker\PageRebuildDataWorker</classname>
+                       <nicevalue>50</nicevalue>
+               </type>
                <type>
                        <name>com.woltlab.wcf.poll</name>
                        <definitionname>com.woltlab.wcf.rebuildData</definitionname>
index b6535b91e610ae7dd464fa301706b0b7db6eda8a..c6ce933f7e5e390fc365bbf7059a5bdc0b841ab7 100644 (file)
@@ -10,6 +10,7 @@ use wcf\system\exception\UserInputException;
 use wcf\system\html\simple\HtmlSimpleParser;
 use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
 use wcf\system\page\handler\ILookupPageHandler;
+use wcf\system\search\SearchIndexManager;
 use wcf\system\WCF;
 
 /**
@@ -83,6 +84,20 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                ]);
                                $pageContentEditor = new PageContentEditor($pageContent);
                                
+                               // update search index
+                               if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                       SearchIndexManager::getInstance()->set(
+                                               'com.woltlab.wcf.page',
+                                               $pageContent->pageContentID,
+                                               $pageContent->content,
+                                               $pageContent->title,
+                                               0,
+                                               null,
+                                               '',
+                                               $languageID ?: null
+                                       );
+                               }
+                               
                                // save embedded objects
                                if (!empty($content['htmlInputProcessor'])) {
                                        /** @noinspection PhpUndefinedMethodInspection */
@@ -168,6 +183,20 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                                $pageContentEditor = new PageContentEditor($pageContent);
                                        }
                                        
+                                       // update search index
+                                       if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                               SearchIndexManager::getInstance()->set(
+                                                       'com.woltlab.wcf.page',
+                                                       $pageContent->pageContentID,
+                                                       $pageContent->content,
+                                                       $pageContent->title,
+                                                       0,
+                                                       null,
+                                                       '',
+                                                       $languageID ?: null
+                                               );
+                                       }
+                                       
                                        // save embedded objects
                                        if (!empty($content['htmlInputProcessor'])) {
                                                /** @noinspection PhpUndefinedMethodInspection */
@@ -313,6 +342,7 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
         * @inheritDoc
         */
        public function delete() {
+               $pageContentIDs = [];
                foreach ($this->getObjects() as $page) {
                        if ($page->pageType == 'tpl') {
                                foreach ($page->getPageContents() as $languageID => $content) {
@@ -322,8 +352,17 @@ class PageAction extends AbstractDatabaseObjectAction implements ISearchAction,
                                        }
                                }
                        }
+                       
+                       foreach ($page->getPageContents() as $pageContent) {
+                               $pageContentIDs[] = $pageContent->pageContentID;
+                       }
                }
                
                parent::delete();
+               
+               if (!empty($pageContentIDs)) {
+                       // delete entry from search index
+                       SearchIndexManager::getInstance()->delete('com.woltlab.wcf.page', $pageContentIDs);
+               }
        }
 }
diff --git a/wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php b/wcfsetup/install/files/lib/data/page/content/SearchResultPageContent.class.php
new file mode 100644 (file)
index 0000000..0a0cdc2
--- /dev/null
@@ -0,0 +1,88 @@
+<?php
+namespace wcf\data\page\content;
+use wcf\data\DatabaseObjectDecorator;
+use wcf\data\search\ISearchResultObject;
+use wcf\system\request\LinkHandler;
+use wcf\system\search\SearchResultTextParser;
+
+/**
+ * Represents an page content as a search result.
+ *
+ * @author     Marcel Werk
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Page\Content
+ * @since      3.1
+ *        
+ * @method     PageContent     getDecoratedObject()
+ * @mixin      PageContent
+ */
+class SearchResultPageContent extends DatabaseObjectDecorator implements ISearchResultObject {
+       /**
+        * @inheritDoc
+        */
+       protected static $baseClass = PageContent::class;
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUserProfile() {
+               return null;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getSubject() {
+               return $this->getDecoratedObject()->title;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTime() {
+               return 0;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getLink($query = '') {
+               return LinkHandler::getInstance()->getCmsLink($this->getDecoratedObject()->pageID, ($this->getDecoratedObject()->languageID ?: -1));
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getObjectTypeName() {
+               return 'com.woltlab.wcf.page';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getFormattedMessage() {
+               if ($this->getDecoratedObject()->pageType == 'text') {
+                       $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getFormattedContent());
+               }
+               else {
+                       $message = SearchResultTextParser::getInstance()->parse($this->getDecoratedObject()->getParsedContent());
+               }
+               
+               return $message;
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContainerTitle() {
+               return '';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getContainerLink() {
+               return '';
+       }
+}
diff --git a/wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php b/wcfsetup/install/files/lib/data/page/content/SearchResultPageContentList.class.php
new file mode 100644 (file)
index 0000000..299a842
--- /dev/null
@@ -0,0 +1,23 @@
+<?php
+namespace wcf\data\page\content;
+
+/**
+ * Represents a list of page content as search results.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\Data\Page\Content
+ * @since      3.1
+ * 
+ * @method     SearchResultPageContent                 current()
+ * @method     SearchResultPageContent[]               getObjects()
+ * @method     SearchResultPageContent|null            search($objectID)
+ * @property   SearchResultPageContent[]               $objects
+ */
+class SearchResultPageContentList extends PageContentList {
+       /**
+        * @inheritDoc
+        */
+       public $decoratorClassName = SearchResultPageContent::class;
+}
index 2472fb71c2f877dbe3b39f69408f2f7a4fb60e47..5174f79aa4895ef1e20021d16e8b07e15af1dc03 100644 (file)
@@ -6,6 +6,7 @@ use wcf\data\page\PageEditor;
 use wcf\system\exception\SystemException;
 use wcf\system\language\LanguageFactory;
 use wcf\system\request\RouteHandler;
+use wcf\system\search\SearchIndexManager;
 use wcf\system\WCF;
 use wcf\util\StringUtil;
 
@@ -25,10 +26,17 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
        public $className = PageEditor::class;
        
        /**
-        * @inheritDoc
+        * page content
+        * @var mixed[]
         */
        protected $content = [];
        
+       /**
+        * pages objects
+        * @var Page[]
+        */
+       protected $pages = [];
+       
        /**
         * @inheritDoc
         */
@@ -273,6 +281,7 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                }
                
                // store content for later import
+               $this->pages[$page->pageID] = $page;
                $this->content[$page->pageID] = $content;
                
                return $page;
@@ -322,6 +331,27 @@ class PagePackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin
                                }
                        }
                        WCF::getDB()->commitTransaction();
+                       
+                       // create search index tables
+                       SearchIndexManager::getInstance()->createSearchIndices();
+                       
+                       // update search index
+                       foreach ($this->pages as $pageID => $page) {
+                               if ($page->pageType == 'text' || $page->pageType == 'html') {
+                                       foreach ($page->getPageContents() as $languageID => $pageContent) {
+                                               SearchIndexManager::getInstance()->set(
+                                                       'com.woltlab.wcf.page',
+                                                       $pageContent->pageContentID,
+                                                       $pageContent->content,
+                                                       $pageContent->title,
+                                                       0,
+                                                       null,
+                                                       '',
+                                                       $languageID ?: null
+                                               );
+                                       }
+                               }
+                       }
                }
        }
 }
diff --git a/wcfsetup/install/files/lib/system/search/PageSearch.class.php b/wcfsetup/install/files/lib/system/search/PageSearch.class.php
new file mode 100644 (file)
index 0000000..b6ce006
--- /dev/null
@@ -0,0 +1,118 @@
+<?php
+namespace wcf\system\search;
+use wcf\data\object\type\ObjectTypeCache;
+use wcf\data\page\content\SearchResultPageContent;
+use wcf\data\page\content\SearchResultPageContentList;
+use wcf\form\IForm;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\WCF;
+
+/**
+ * An implementation of ISearchableObjectType for searching in cms pages.
+ *
+ * @author      Marcel Werk
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Search
+ * @since      3.1
+ */
+class PageSearch extends AbstractSearchableObjectType {
+       /**
+        * message data cache
+        * @var SearchResultPageContent[]
+        */
+       public $messageCache = [];
+       
+       /**
+        * @inheritDoc
+        */
+       public function cacheObjects(array $objectIDs, array $additionalData = null) {
+               $list = new SearchResultPageContentList();
+               $list->setObjectIDs($objectIDs);
+               $list->readObjects();
+               foreach ($list->getObjects() as $content) {
+                       $this->messageCache[$content->pageContentID] = $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.'_page_content';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getIDFieldName() {
+               return $this->getTableName().'.pageContentID';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getSubjectFieldName() {
+               return $this->getTableName().'.title';
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getUsernameFieldName() {
+               return "''";
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function getTimeFieldName() {
+               return 'wcf'.WCF_N.'_page_content.pageContentID';
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getConditions(IForm $form = null) {
+               $conditionBuilder = new PreparedStatementConditionBuilder();
+               $conditionBuilder->add('wcf'.WCF_N.'_page.pageType IN (?) AND wcf'.WCF_N.'_page.isDisabled = ?', [['text', 'html'], 0]);
+               
+               // acl
+               $objectTypeID = ObjectTypeCache::getInstance()->getObjectTypeIDByName('com.woltlab.wcf.acl.simple', 'com.woltlab.wcf.page');
+               $conditionBuilder->add('(       
+                       wcf'.WCF_N.'_page_content.pageID NOT IN (
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_group WHERE objectTypeID = ?
+                               UNION
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_user WHERE objectTypeID = ?
+                       )
+                       OR
+                       wcf'.WCF_N.'_page_content.pageID IN (
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_group WHERE objectTypeID = ? AND groupID IN (?)
+                               UNION
+                               SELECT objectID FROM wcf' . WCF_N . '_acl_simple_to_user WHERE objectTypeID = ? AND userID = ?
+                       )
+               )', [$objectTypeID, $objectTypeID, $objectTypeID, WCF::getUser()->getGroupIDs(), $objectTypeID, WCF::getUser()->userID]);
+               
+               return $conditionBuilder;
+       }
+       
+       /** @noinspection PhpMissingParentCallCommonInspection */
+       /**
+        * @inheritDoc
+        */
+       public function getJoins() {
+               return 'INNER JOIN wcf'.WCF_N.'_page ON (wcf'.WCF_N.'_page.pageID = '.$this->getTableName().'.pageID)';
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php b/wcfsetup/install/files/lib/system/worker/PageRebuildDataWorker.class.php
new file mode 100644 (file)
index 0000000..abd669f
--- /dev/null
@@ -0,0 +1,108 @@
+<?php
+namespace wcf\system\worker;
+use wcf\data\page\content\PageContentEditor;
+use wcf\data\page\content\PageContentList;
+use wcf\data\page\PageList;
+use wcf\system\html\input\HtmlInputProcessor;
+use wcf\system\message\embedded\object\MessageEmbeddedObjectManager;
+use wcf\system\search\SearchIndexManager;
+
+/**
+ * Worker implementation for updating cms pages.
+ * 
+ * @author     Marcel Werk
+ * @copyright  2001-2017 WoltLab GmbH
+ * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package    WoltLabSuite\Core\System\Worker
+ * @since      3.1
+ * 
+ * @method     PageList        getObjectList()
+ */
+class PageRebuildDataWorker extends AbstractRebuildDataWorker {
+       /**
+        * @inheritDoc
+        */
+       protected $objectListClassName = PageList::class;
+       
+       /**
+        * @inheritDoc
+        */
+       protected $limit = 100;
+       
+       /**
+        * @var HtmlInputProcessor
+        */
+       protected $htmlInputProcessor;
+       
+       /**
+        * @inheritDoc
+        */
+       protected function initObjectList() {
+               parent::initObjectList();
+               
+               $this->objectList->sqlOrderBy = 'page.pageID';
+               $this->objectList->getConditionBuilder()->add('page.pageType <> ?', ['system']);
+       }
+       
+       /**
+        * @inheritDoc
+        */
+       public function execute() {
+               parent::execute();
+               
+               if (!$this->loopCount) {
+                       // reset search index
+                       SearchIndexManager::getInstance()->reset('com.woltlab.wcf.page');
+               }
+               
+               if (!count($this->objectList)) {
+                       return;
+               }
+               $pages = $this->objectList->getObjects();
+               
+               // update page content
+               $pageContentList = new PageContentList();
+               $pageContentList->getConditionBuilder()->add('page_content.pageID IN (?)', [$this->objectList->getObjectIDs()]);
+               $pageContentList->readObjects();
+               foreach ($pageContentList as $pageContent) {
+                       $page = $pages[$pageContent->pageID];
+                       if ($page->pageType == 'text' || $page->pageType == 'html') {
+                               // update search index
+                               SearchIndexManager::getInstance()->set(
+                                       'com.woltlab.wcf.page',
+                                       $pageContent->pageContentID,
+                                       $pageContent->content,
+                                       $pageContent->title,
+                                       0,
+                                       null,
+                                       '',
+                                       $pageContent->languageID
+                               );
+                       }
+                       
+                       // update embedded objects
+                       $this->getHtmlInputProcessor()->processEmbeddedContent($pageContent->content, 'com.woltlab.wcf.page.content', $pageContent->pageContentID);
+                       
+                       $hasEmbeddedObjects = 0;
+                       if (MessageEmbeddedObjectManager::getInstance()->registerObjects($this->getHtmlInputProcessor())) {
+                               $hasEmbeddedObjects = 1;
+                       }
+                       
+                       if ($hasEmbeddedObjects != $pageContent->hasEmbeddedObjects) {
+                               $pageContentEditor = new PageContentEditor($pageContent);
+                               $pageContentEditor->update(['hasEmbeddedObjects' => $hasEmbeddedObjects]);
+                       }
+               }
+       }
+       
+       /**
+        * @return HtmlInputProcessor
+        */
+       protected function getHtmlInputProcessor() {
+               if ($this->htmlInputProcessor === null) {
+                       $this->htmlInputProcessor = new HtmlInputProcessor();
+               }
+               
+               return $this->htmlInputProcessor;
+       }
+}
index 323634fd4be276253435013f99cf3c87eb33b1f2..020819d47d8befbf2fd90d92ee80d01d984693aa 100644 (file)
@@ -1514,6 +1514,8 @@ GmbH=Gesellschaft mit beschränkter Haftung]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warnung: Die Ausführung dieser Aktion kann bei umfangreichen Datenbanken sehr lange dauern.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Kommentare aktualisieren]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Aktualisiert die Kommentare]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Seiten aktualisieren]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Aktualisiert den Suchindex für CMS-Seiten]]></item>
        </category>
        
        <category name="wcf.acp.rescueMode">
@@ -2987,6 +2989,8 @@ Fehler sind beispielsweise:
                <item name="wcf.search.error.user.noMatches"><![CDATA[Es wurde kein Eintrag von diesem Autor gefunden.]]></item>
                <item name="wcf.search.object.com.woltlab.wcf.article"><![CDATA[Artikel]]></item>
                <item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Artikel]]></item>
+               <item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Seite]]></item>
+               <item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Seiten]]></item>
        </category>
        
        <category name="wcf.style">
index a433600359b391badbc65636b72d95a9938529d2..1cdcedcd5ea02df89b21b430442c3d06ed768246 100644 (file)
@@ -1475,6 +1475,8 @@ Examples for medium ID detection:
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.databaseConvertEncoding.description"><![CDATA[Warning: This action may take a while to complete on large databases.]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment"><![CDATA[Rebuild Comments]]></item>
                <item name="wcf.acp.rebuildData.com.woltlab.wcf.comment.description"><![CDATA[Rebuilds the comments.]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page"><![CDATA[Rebuild Pages]]></item>
+               <item name="wcf.acp.rebuildData.com.woltlab.wcf.page.description"><![CDATA[Rebuilds the page search index.]]></item>
        </category>
        
        <category name="wcf.acp.rescueMode">
@@ -2980,6 +2982,8 @@ Errors are:
                <item name="wcf.search.error.user.noMatches"><![CDATA[There were no items matching this author.]]></item>
                <item name="wcf.search.object.com.woltlab.wcf.article"><![CDATA[Article]]></item>
                <item name="wcf.search.type.com.woltlab.wcf.article"><![CDATA[Articles]]></item>
+               <item name="wcf.search.object.com.woltlab.wcf.page"><![CDATA[Page]]></item>
+               <item name="wcf.search.type.com.woltlab.wcf.page"><![CDATA[Pages]]></item>
        </category>
        
        <category name="wcf.style">