Search overhaul (WIP)
authorAlexander Ebert <ebert@woltlab.com>
Thu, 4 Sep 2014 17:55:42 +0000 (19:55 +0200)
committerAlexander Ebert <ebert@woltlab.com>
Thu, 4 Sep 2014 17:55:42 +0000 (19:55 +0200)
13 files changed:
com.woltlab.wcf/option.xml
wcfsetup/install/files/lib/acp/action/InstallPackageAction.class.php
wcfsetup/install/files/lib/form/SearchForm.class.php
wcfsetup/install/files/lib/system/message/embedded/object/AttachmentMessageEmbeddedObjectHandler.class.php
wcfsetup/install/files/lib/system/search/AbstractSearchEngine.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/AbstractSearchIndexManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/ISearchEngine.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/ISearchIndexManager.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/SearchEngine.class.php
wcfsetup/install/files/lib/system/search/SearchIndexManager.class.php
wcfsetup/install/files/lib/system/search/mysql/MysqlSearchEngine.class.php [new file with mode: 0644]
wcfsetup/install/files/lib/system/search/mysql/MysqlSearchIndexManager.class.php [new file with mode: 0644]
wcfsetup/install/files/options.inc.php

index 3de93ba5a89a71d0dd6a1d5519781acecec6c3c0..46233f57efa344e383b25bc91621165a4e859222 100644 (file)
@@ -54,6 +54,9 @@
                                        <category name="general.system.image">
                                                <parent>general.system</parent>
                                        </category>
+                                       <category name="general.system.search">
+                                               <parent>general.system</parent>
+                                       </category>
                                        <category name="general.system.date">
                                                <parent>general.system</parent>
                                        </category>
@@ -455,6 +458,15 @@ imagick:wcf.acp.option.image_adapter_type.imagick]]>
                        </option>
                        <!-- /general.system.image -->
                        
+                       <!-- general.system.search -->
+                       <option name="search_engine">
+                               <categoryname>general.system.search</categoryname>
+                               <optiontype>radioButton</optiontype>
+                               <defaultvalue><![CDATA[mysql]]></defaultvalue>
+                               <selectoptions><![CDATA[mysql:wcf.acp.option.search_engine.mysql]]></selectoptions>
+                       </option>
+                       <!-- /general.system.search -->
+                       
                        <!-- general.system.cookie -->
                        <option name="cookie_prefix">
                                <categoryname>general.system.cookie</categoryname>
index 2f498fdb933a2e8b6bee3a750d1aeb1ad797db30..2b943c3bc16b738cd9e09738265a4ced5468ce87 100755 (executable)
@@ -231,7 +231,7 @@ class InstallPackageAction extends AbstractDialogAction {
         */
        protected function finalize() {
                // create search index tables
-               SearchIndexManager::createSearchIndexTables();
+               SearchIndexManager::getInstance()->createSearchIndices();
                
                CacheHandler::getInstance()->flushAll();
        }
index eaf456ca92478905d6ec9549166122ca0dc24ef7..2ba150d69ca3cb14f1596892d663a5b1a7772218 100644 (file)
@@ -3,7 +3,6 @@ namespace wcf\form;
 use wcf\data\search\Search;
 use wcf\data\search\SearchAction;
 use wcf\system\application\ApplicationHandler;
-use wcf\system\database\util\PreparedStatementConditionBuilder;
 use wcf\system\exception\IllegalLinkException;
 use wcf\system\exception\NamedUserException;
 use wcf\system\exception\PermissionDeniedException;
@@ -115,6 +114,12 @@ class SearchForm extends AbstractCaptchaForm {
         */
        public $searchIndexCondition = null;
        
+       /**
+        * class name for $searchIndexCondition object
+        * @var string
+        */
+       public $searchIndexConditionClassName = 'wcf\system\database\util\PreparedStatementConditionBuilder';
+       
        /**
         * search hash to modify existing search
         * @var string
@@ -414,7 +419,7 @@ class SearchForm extends AbstractCaptchaForm {
                
                // default conditions
                $userIDs = $this->getUserIDs();
-               $this->searchIndexCondition = new PreparedStatementConditionBuilder(false);
+               $this->searchIndexCondition = new $this->searchIndexConditionClassName(false);
                
                // user ids
                if (!empty($userIDs)) {
index 79003b5a7bf49648b32effa5a37bf48499dd23d5..ade25c57ffb3daf68892dfd525946c149ee23c9d 100644 (file)
@@ -26,10 +26,13 @@ class AttachmentMessageEmbeddedObjectHandler extends AbstractMessageEmbeddedObje
                                if ($parsedAttachmentID) $attachmentIDs[] = $parsedAttachmentID;
                        }
                        
-                       $attachmentList = new AttachmentList();
-                       $attachmentList->getConditionBuilder()->add("attachment.attachmentID IN (?)", array($attachmentIDs));
-                       $attachmentList->readObjectIDs();
-                       return $attachmentList->getObjectIDs();
+                       if (!empty($attachmentIDs)) {
+                               $attachmentList = new AttachmentList();
+                               $attachmentList->getConditionBuilder()->add("attachment.attachmentID IN (?)", array($attachmentIDs));
+                               $attachmentList->readObjectIDs();
+                               
+                               return $attachmentList->getObjectIDs();
+                       }
                }
                
                return false;
diff --git a/wcfsetup/install/files/lib/system/search/AbstractSearchEngine.class.php b/wcfsetup/install/files/lib/system/search/AbstractSearchEngine.class.php
new file mode 100644 (file)
index 0000000..f1ba8fe
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+namespace wcf\system\search;
+use wcf\system\SingletonFactory;
+
+/**
+ * Default implementation for search engines, this class should be extended by
+ * all search engines to preserve compatibility in case of interface changes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+abstract class AbstractSearchEngine extends SingletonFactory implements ISearchEngine { }
diff --git a/wcfsetup/install/files/lib/system/search/AbstractSearchIndexManager.class.php b/wcfsetup/install/files/lib/system/search/AbstractSearchIndexManager.class.php
new file mode 100644 (file)
index 0000000..3449d5d
--- /dev/null
@@ -0,0 +1,49 @@
+<?php
+namespace wcf\system\search;
+use wcf\data\object\type\ObjectType;
+use wcf\data\object\type\ObjectTypeList;
+use wcf\system\SingletonFactory;
+use wcf\system\WCF;
+
+/**
+ * Default implementation for search index managers, this class should be extended by
+ * all search index managers to preserve compatibility in case of interface changes.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+abstract class AbstractSearchIndexManager extends SingletonFactory implements ISearchIndexManager {
+       /**
+        * @see \wcf\system\search\ISearchIndexManager::createSearchIndices()
+        */
+       public function createSearchIndices() {
+               // get definition id
+               $sql = "SELECT  definitionID
+                       FROM    wcf".WCF_N."_object_type_definition
+                       WHERE   definitionName = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array('com.woltlab.wcf.searchableObjectType'));
+               $row = $statement->fetchArray();
+               
+               $objectTypeList = new ObjectTypeList();
+               $objectTypeList->getConditionBuilder()->add("object_type.definitionID = ?", array($row['definitionID']));
+               $objectTypeList->readObjects();
+               
+               foreach ($objectTypeList as $objectType) {
+                       $this->createSearchIndex($objectType);
+               }
+       }
+       
+       /**
+        * Creates the search index for given object type. Returns true if the
+        * index was created, otherwise false.
+        * 
+        * @param       \wcf\data\object\type\ObjectType        $objectType
+        * @return      boolean
+        */
+       abstract protected function createSearchIndex(ObjectType $objectType);
+}
diff --git a/wcfsetup/install/files/lib/system/search/ISearchEngine.class.php b/wcfsetup/install/files/lib/system/search/ISearchEngine.class.php
new file mode 100644 (file)
index 0000000..e639875
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+namespace wcf\system\search;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+
+/**
+ * Default interface for search engines.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+interface ISearchEngine {
+       /**
+        * Searches for the given string and returns the data of the found messages.
+        *
+        * @param       string                                                          $q
+        * @param       array                                                           $objectTypes
+        * @param       boolean                                                         $subjectOnly
+        * @param       \wcf\system\database\util\PreparedStatementConditionBuilder     $searchIndexCondition
+        * @param       array                                                           $additionalConditions
+        * @param       string                                                          $orderBy
+        * @param       integer                                                         $limit
+        * @return      array
+        */
+       public function search($q, array $objectTypes, $subjectOnly = false, PreparedStatementConditionBuilder $searchIndexCondition = null, array $additionalConditions = array(), $orderBy = 'time DESC', $limit = 1000);
+}
diff --git a/wcfsetup/install/files/lib/system/search/ISearchIndexManager.class.php b/wcfsetup/install/files/lib/system/search/ISearchIndexManager.class.php
new file mode 100644 (file)
index 0000000..0f26731
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+namespace wcf\system\search;
+
+/**
+ * Default interface for search index managers.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+interface ISearchIndexManager {
+       /**
+        * Adds a new entry.
+        * 
+        * @param       string          $objectType
+        * @param       integer         $objectID
+        * @param       string          $message
+        * @param       string          $subject
+        * @param       integer         $time
+        * @param       integer         $userID
+        * @param       string          $username
+        * @param       integer         $languageID
+        * @param       string          $metaData
+        * @param       array<mixed>    $additionalData
+        */
+       public function add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array());
+       
+       /**
+        * Updates the search index.
+        * 
+        * @param       string          $objectType
+        * @param       integer         $objectID
+        * @param       string          $message
+        * @param       string          $subject
+        * @param       integer         $time
+        * @param       integer         $userID
+        * @param       string          $username
+        * @param       integer         $languageID
+        * @param       string          $metaData
+        * @param       array<mixed>    $additionalData
+        */
+       public function update($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array());
+       
+       /**
+        * Deletes search index entries.
+        * 
+        * @param       string          $objectType
+        * @param       array<integer>  $objectIDs
+        */
+       public function delete($objectType, array $objectIDs);
+       
+       /**
+        * Resets the search index.
+        * 
+        * @param       string          $objectType
+        */
+       public function reset($objectType);
+       
+       /**
+        * Creates the search index for all searchable objects.
+        */
+       public function createSearchIndices();
+}
index c6370063cba2a01e9a4459745d13f8ee8ab02594..fafabced1b96d351cb28cd8a9ccc2c4202a795d4 100644 (file)
@@ -2,11 +2,7 @@
 namespace wcf\system\search;
 use wcf\data\object\type\ObjectTypeCache;
 use wcf\system\database\util\PreparedStatementConditionBuilder;
-use wcf\system\database\DatabaseException;
-use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
-use wcf\system\WCF;
-use wcf\util\StringUtil;
 
 /**
  * SearchEngine searches for given query in the selected object types.
@@ -18,7 +14,7 @@ use wcf\util\StringUtil;
  * @subpackage system.search
  * @category   Community Framework
  */
-class SearchEngine extends SingletonFactory {
+class SearchEngine extends SingletonFactory implements ISearchEngine {
        /**
         * list of available object types
         * @var array
@@ -26,10 +22,10 @@ class SearchEngine extends SingletonFactory {
        protected $availableObjectTypes = array();
        
        /**
-        * MySQL's minimum word length for fulltext indices
-        * @var integer
+        * search engine object
+        * @var \wcf\system\search\ISearchEngine
         */
-       protected static $ftMinWordLen = null;
+       protected $searchEngine = null;
        
        /**
         * @see \wcf\system\SingletonFactory::init()
@@ -68,226 +64,35 @@ class SearchEngine extends SingletonFactory {
        }
        
        /**
-        * Searches for the given string and returns the data of the found messages.
+        * Returns the search engine object.
         * 
-        * @param       string                                                          $q
-        * @param       array                                                           $objectTypes
-        * @param       boolean                                                         $subjectOnly
-        * @param       \wcf\system\database\util\PreparedStatementConditionBuilder     $searchIndexCondition
-        * @param       array                                                           $additionalConditions
-        * @param       string                                                          $orderBy
-        * @param       integer                                                         $limit
-        * @return      array
+        * @return      \wcf\system\search\ISearchEngine
         */
-       public function search($q, array $objectTypes, $subjectOnly = false, PreparedStatementConditionBuilder $searchIndexCondition = null, array $additionalConditions = array(), $orderBy = 'time DESC', $limit = 1000) {
-               // handle sql types
-               $fulltextCondition = null;
-               $relevanceCalc = '';
-               if (!empty($q)) {
-                       $q = $this->parseSearchQuery($q);
-                       
-                       $fulltextCondition = new PreparedStatementConditionBuilder(false);
-                       switch (WCF::getDB()->getDBType()) {
-                               case 'wcf\system\database\MySQLDatabase':
-                                       $fulltextCondition->add("MATCH (subject".(!$subjectOnly ? ', message, metaData' : '').") AGAINST (? IN BOOLEAN MODE)", array($q));
-                               break;
-                               
-                               case 'wcf\system\database\PostgreSQLDatabase':
-                                       // replace * with :*
-                                       $q = str_replace('*', ':*', $q);
-                                       
-                                       $fulltextCondition->add("fulltextIndex".($subjectOnly ? "SubjectOnly" : '')." @@ to_tsquery(?)", array($q));
-                               break;
-                               
-                               default:
-                                       throw new SystemException("your database type doesn't support fulltext search");
-                       }
-                       
-                       if ($orderBy == 'relevance ASC' || $orderBy == 'relevance DESC') {
-                               switch (WCF::getDB()->getDBType()) {
-                                       case 'wcf\system\database\MySQLDatabase':
-                                               $relevanceCalc = "MATCH (subject".(!$subjectOnly ? ', message, metaData' : '').") AGAINST ('".escapeString($q)."') + (5 / (1 + POW(LN(1 + (".TIME_NOW." - time) / 2592000), 2))) AS relevance";
-                                       break;
-                                       
-                                       case 'wcf\system\database\PostgreSQLDatabase':
-                                               $relevanceCalc = "ts_rank_cd(fulltextIndex".($subjectOnly ? "SubjectOnly" : '').", '".escapeString($q)."') AS relevance";
-                                       break;
+       protected function getSearchEngine() {
+               if ($this->searchEngine === null) {
+                       $className = '';
+                       if (SEARCH_ENGINE != 'mysql') {
+                               $className = 'wcf\system\search\\'.SEARCH_ENGINE.'\\'.ucfirst(SEARCH_ENGINE).'SearchEngine';
+                               if (!class_exists($className)) {
+                                       $className = '';
                                }
                        }
-               }
-               
-               // build search query
-               $sql = '';
-               $parameters = array();
-               foreach ($objectTypes as $objectTypeName) {
-                       $objectType = $this->getObjectType($objectTypeName);
-                       if (!empty($sql)) $sql .= "\nUNION\n";
-                       $additionalConditionsConditionBuilder = (isset($additionalConditions[$objectTypeName]) ? $additionalConditions[$objectTypeName] : null);
-                       if (($specialSQL = $objectType->getSpecialSQLQuery($fulltextCondition, $searchIndexCondition, $additionalConditionsConditionBuilder, $orderBy))) {
-                               $sql .= "(".$specialSQL.")";
-                       }
-                       else {
-                               $sql .= "(
-                                       SELECT          ".$objectType->getIDFieldName()." AS objectID,
-                                                       ".$objectType->getSubjectFieldName()." AS subject,
-                                                       ".$objectType->getTimeFieldName()." AS time,
-                                                       ".$objectType->getUsernameFieldName()." AS username,
-                                                       '".$objectTypeName."' AS objectType
-                                                       ".($relevanceCalc ? ',search_index.relevance' : '')."
-                                       FROM            ".$objectType->getTableName()."
-                                       INNER JOIN      (
-                                                               SELECT          objectID
-                                                                               ".($relevanceCalc ? ','.$relevanceCalc : '')."
-                                                               FROM            ".SearchIndexManager::getTableName($objectTypeName)."
-                                                               WHERE           ".($fulltextCondition !== null ? $fulltextCondition : '')."
-                                                                               ".(($searchIndexCondition !== null && $searchIndexCondition->__toString()) ? ($fulltextCondition !== null ? "AND " : '').$searchIndexCondition : '')."
-                                                               ".(!empty($orderBy) && $fulltextCondition === null ? 'ORDER BY '.$orderBy : '')."
-                                                               LIMIT           1000
-                                                       ) search_index
-                                       ON              (".$objectType->getIDFieldName()." = search_index.objectID)
-                                       ".$objectType->getJoins()."
-                                       ".(isset($additionalConditions[$objectTypeName]) ? $additionalConditions[$objectTypeName] : '')."
-                               )";
-                       }
-                       
-                       if ($fulltextCondition !== null) $parameters = array_merge($parameters, $fulltextCondition->getParameters());
-                       if ($searchIndexCondition !== null) $parameters = array_merge($parameters, $searchIndexCondition->getParameters());
-                       if (isset($additionalConditions[$objectTypeName])) $parameters = array_merge($parameters, $additionalConditions[$objectTypeName]->getParameters());
-               }
-               if (empty($sql)) {
-                       throw new SystemException('no object types given');
-               }
-               
-               if (!empty($orderBy)) {
-                       $sql .= " ORDER BY " . $orderBy;
-               }
-               
-               // send search query
-               $messages = array();
-               $statement = WCF::getDB()->prepareStatement($sql, $limit);
-               $statement->execute($parameters);
-               while ($row = $statement->fetchArray()) {
-                       $messages[] = array(
-                               'objectID' => $row['objectID'],
-                               'objectType' => $row['objectType']
-                       );
-               }
-               
-               return $messages;
-       }
-       
-       /**
-        * Manipulates the search term (< and > used as quotation marks):
-        * 
-        * - <test foo> becomes <+test* +foo*>
-        * - <test -foo bar> becomes <+test* -foo* +bar*>
-        * - <test "foo bar"> becomes <+test* +"foo bar">
-        * 
-        * @see http://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html
-        * 
-        * @param       string          $query
-        */
-       protected function parseSearchQuery($query) {
-               $query = StringUtil::trim($query);
-               
-               // expand search terms with a * unless they're encapsulated with quotes
-               $inQuotes = false;
-               $previousChar = $tmp = '';
-               $controlCharacterOrSpace = false;
-               $chars = array('+', '-', '*');
-               $ftMinWordLen = self::getFulltextMinimumWordLength();
-               for ($i = 0, $length = mb_strlen($query); $i < $length; $i++) {
-                       $char = mb_substr($query, $i, 1);
-                       
-                       if ($inQuotes) {
-                               if ($char == '"') {
-                                       $inQuotes = false;
-                               }
-                       }
-                       else {
-                               if ($char == '"') {
-                                       $inQuotes = true;
-                               }
-                               else {
-                                       if ($char == ' ' && !$controlCharacterOrSpace) {
-                                               $controlCharacterOrSpace = true;
-                                               $tmp .= '*';
-                                       }
-                                       else if (in_array($char, $chars)) {
-                                               $controlCharacterOrSpace = true;
-                                       }
-                                       else {
-                                               $controlCharacterOrSpace = false;
-                                       }
-                               }
-                       }
-                       
-                       /*
-                        * prepend a plus sign (logical AND) if ALL these conditions are given:
-                        * 
-                        * 1) previous character:
-                        *   - is empty (start of string)
-                        *   - is a space (MySQL uses spaces to separate words)
-                        * 
-                        * 2) not within quotation marks
-                        * 
-                        * 3) current char:
-                        *   - is NOT +, - or *
-                        */
-                       if (($previousChar == '' || $previousChar == ' ') && !$inQuotes && !in_array($char, $chars)) {
-                               // check if the term is shorter than MySQL's ft_min_word_len
                                
-                               if ($i + $ftMinWordLen <= $length) {
-                                       $term = '';// $char;
-                                       for ($j = $i, $innerLength = $ftMinWordLen + $i; $j < $innerLength; $j++) {
-                                               $currentChar = mb_substr($query, $j, 1);
-                                               if ($currentChar == '"' || $currentChar == ' ' || in_array($currentChar, $chars)) {
-                                                       break;
-                                               }
-                                               
-                                               $term .= $currentChar;
-                                       }
-                                       
-                                       if (mb_strlen($term) == $ftMinWordLen) {
-                                               $tmp .= '+';
-                                       }
-                               }
+                       // fallback to MySQL
+                       if (empty($className)) {
+                               $className = 'wcf\system\search\mysql\MysqlSearchEngine';
                        }
-                       
-                       $tmp .= $char;
-                       $previousChar = $char;
-               }
-               
-               // handle last char
-               if (!$inQuotes && !$controlCharacterOrSpace) {
-                       $tmp .= '*';
+                               
+                       $this->searchEngine = call_user_func(array($className, 'getInstance'));
                }
-               
-               return $tmp;
+       
+               return $this->searchEngine;
        }
        
        /**
-        * Returns MySQL's minimum word length for fulltext indices.
-        * 
-        * @return      integer
+        * @see \wcf\system\search\ISearchEngine::search()
         */
-       public static function getFulltextMinimumWordLength() {
-               if (self::$ftMinWordLen === null) {
-                       $sql = "SHOW VARIABLES LIKE ?";
-                       $statement = WCF::getDB()->prepareStatement($sql);
-                       
-                       try {
-                               $statement->execute(array('ft_min_word_len'));
-                               $row = $statement->fetchArray();
-                       }
-                       catch (DatabaseException $e) {
-                               // fallback if user is disallowed to issue 'SHOW VARIABLES'
-                               $row = array('Value' => 4);
-                       }
-                       
-                       self::$ftMinWordLen = $row['Value'];
-               }
-               
-               return self::$ftMinWordLen;
+       public function search($q, array $objectTypes, $subjectOnly = false, PreparedStatementConditionBuilder $searchIndexCondition = null, array $additionalConditions = array(), $orderBy = 'time DESC', $limit = 1000) {
+               return $this->getSearchEngine()->search($q, $objectTypes, $subjectOnly, $searchIndexCondition, $additionalConditions, $orderBy, $limit);
        }
 }
index cdcf593c9aceafc2fa2cda36da6ec39fa28cba08..961ac96f2558d7b3671aa68cf50e22de09867648 100644 (file)
@@ -1,13 +1,10 @@
 <?php
 namespace wcf\system\search;
-use wcf\data\object\type\ObjectType;
 use wcf\data\object\type\ObjectTypeCache;
-use wcf\data\object\type\ObjectTypeList;
 use wcf\data\package\Package;
 use wcf\data\package\PackageList;
 use wcf\system\exception\SystemException;
 use wcf\system\SingletonFactory;
-use wcf\system\WCF;
 
 /**
  * Manages the search index.
@@ -19,7 +16,7 @@ use wcf\system\WCF;
  * @subpackage system.search
  * @category   Community Framework
  */
-class SearchIndexManager extends SingletonFactory {
+class SearchIndexManager extends SingletonFactory implements ISearchIndexManager {
        /**
         * list of available object types
         * @var array
@@ -32,6 +29,12 @@ class SearchIndexManager extends SingletonFactory {
         */
        protected static $packages = array();
        
+       /**
+        * search index manager object
+        * @var \wcf\system\search\ISearchIndexManager
+        */
+       protected $searchIndexManager = null;
+       
        /**
         * @see \wcf\system\SingletonFactory::init()
         */
@@ -69,159 +72,64 @@ class SearchIndexManager extends SingletonFactory {
        }
        
        /**
-        * Adds a new entry.
+        * Returns the search index manager object.
         * 
-        * @param       string          $objectType
-        * @param       integer         $objectID
-        * @param       string          $message
-        * @param       string          $subject
-        * @param       integer         $time
-        * @param       integer         $userID
-        * @param       string          $username
-        * @param       integer         $languageID
-        * @param       string          $metaData
+        * @return      \wcf\system\search\ISearchIndexManager
         */
-       public function add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '') {
-               if ($languageID === null) $languageID = 0;
+       protected function getSearchIndexManager() {
+               if ($this->searchIndexManager === null) {
+                       $className = '';
+                       if (SEARCH_ENGINE != 'mysql') {
+                               $className = 'wcf\system\search\\'.SEARCH_ENGINE.'\\'.ucfirst(SEARCH_ENGINE).'SearchEngine';
+                               if (!class_exists($className)) {
+                                       $className = '';
+                               }
+                       }
+                       
+                       // fallback to MySQL
+                       if (empty($className)) {
+                               $className = 'wcf\system\search\mysql\MysqlSearchIndexManager';
+                       }
+                       
+                       $this->searchIndexManager = call_user_func(array($className, 'getInstance'));
+               }
                
-               // save new entry
-               $sql = "REPLACE INTO    " . self::getTableName($objectType) . "
-                                       (objectID, subject, message, time, userID, username, languageID, metaData)
-                       VALUES          (?, ?, ?, ?, ?, ?, ?, ?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array($objectID, $subject, $message, $time, $userID, $username, $languageID, $metaData));
+               return $this->searchIndexManager;
        }
        
        /**
-        * Updates the search index.
-        * 
-        * @param       string          $objectType
-        * @param       integer         $objectID
-        * @param       string          $message
-        * @param       string          $subject
-        * @param       integer         $time
-        * @param       integer         $userID
-        * @param       string          $username
-        * @param       integer         $languageID
-        * @param       string          $metaData
+        * @see \wcf\system\search\ISearchIndexManager::add()
         */
-       public function update($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '') {
-               // delete existing entry
-               $this->delete($objectType, array($objectID));
-               
-               // save new entry
-               $this->add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID, $metaData);
+       public function add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array()) {
+               $this->getSearchIndexManager()->add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID, $metaData, $additionalData);
        }
        
        /**
-        * Deletes search index entries.
-        * 
-        * @param       string          $objectType
-        * @param       array<integer>  $objectIDs
+        * @see \wcf\system\search\ISearchIndexManager::update()
         */
-       public function delete($objectType, array $objectIDs) {
-               $sql = "DELETE FROM     " . self::getTableName($objectType) . "
-                       WHERE           objectID = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               WCF::getDB()->beginTransaction();
-               foreach ($objectIDs as $objectID) {
-                       $statement->execute(array($objectID));
-               }
-               WCF::getDB()->commitTransaction();
+       public function update($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array()) {
+               $this->getSearchIndexManager()->update($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID, $metaData, $additionalData);
        }
        
        /**
-        * Resets the search index.
-        * 
-        * @param       string          $objectType
+        * @see \wcf\system\search\ISearchIndexManager::delete()
         */
-       public function reset($objectType) {
-               $sql = "TRUNCATE TABLE " . self::getTableName($objectType);
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute();
+       public function delete($objectType, array $objectIDs) {
+               $this->getSearchIndexManager()->delete($objectType, $objectIDs);
        }
        
        /**
-        * Creates the search index tables for all registered, searchable object types.
+        * @see \wcf\system\search\ISearchIndexManager::reset()
         */
-       public static function createSearchIndexTables() {
-               // get definition id
-               $sql = "SELECT  definitionID
-                       FROM    wcf".WCF_N."_object_type_definition
-                       WHERE   definitionName = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array('com.woltlab.wcf.searchableObjectType'));
-               $row = $statement->fetchArray();
-               
-               $objectTypeList = new ObjectTypeList();
-               $objectTypeList->getConditionBuilder()->add("object_type.definitionID = ?", array($row['definitionID']));
-               $objectTypeList->readObjects();
-               
-               foreach ($objectTypeList as $objectType) {
-                       self::createSearchIndexTable($objectType);
-               }
+       public function reset($objectType) {
+               $this->getSearchIndexManager()->reset($objectType);
        }
        
        /**
-        * Creates the search index table for given object type. Returns true if the
-        * table was created, otherwise false.
-        * 
-        * @param       \wcf\data\object\type\ObjectType        $objectType
-        * @return      boolean
+        * @see \wcf\system\search\ISearchIndexManager::createSearchIndices()
         */
-       protected static function createSearchIndexTable(ObjectType $objectType) {
-               $tableName = self::getTableName($objectType);
-               
-               // check if table already exists
-               $sql = "SELECT  COUNT(*) AS count
-                       FROM    wcf".WCF_N."_package_installation_sql_log
-                       WHERE   sqlTable = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array($tableName));
-               $row = $statement->fetchArray();
-               if ($row['count']) {
-                       // table already exists
-                       return false;
-               }
-               
-               $columns = array(
-                       array('name' => 'objectID', 'data' => array('length' => 10, 'notNull' => true, 'type' => 'int')),
-                       array('name' => 'subject', 'data' => array('default' => '', 'length' => 255, 'notNull' => true, 'type' => 'varchar')),
-                       array('name' => 'message', 'data' => array('type' => 'mediumtext')),
-                       array('name' => 'metaData', 'data' => array('type' => 'mediumtext')),
-                       array('name' => 'time', 'data' => array('default' => 0, 'length' => 10, 'notNull' => true, 'type' => 'int')),
-                       array('name' => 'userID', 'data' => array('default' => '', 'length' => 10, 'type' => 'int')),
-                       array('name' => 'username', 'data' => array('default' => '', 'length' => 255,'notNull' => true, 'type' => 'varchar')),
-                       array('name' => 'languageID', 'data' => array('default' => 0, 'length' => 10, 'notNull' => true, 'type' => 'int'))
-               );
-               
-               $indices = array(
-                       array('name' => 'objectAndLanguage', 'data' => array('columns' => 'objectID, languageID', 'type' => 'UNIQUE')),
-                       array('name' => 'fulltextIndex', 'data' => array('columns' => 'subject, message, metaData', 'type' => 'FULLTEXT')),
-                       array('name' => 'fulltextIndexSubjectOnly', 'data' => array('columns' => 'subject', 'type' => 'FULLTEXT')),
-                       array('name' => 'language', 'data' => array('columns' => 'languageID', 'type' => 'KEY')),
-                       array('name' => 'user', 'data' => array('columns' => 'userID, time', 'type'=> 'KEY'))
-               );
-               
-               WCF::getDB()->getEditor()->createTable($tableName, $columns, $indices);
-               
-               // add comment
-               $sql = "ALTER TABLE     ".$tableName."
-                       COMMENT         = ?";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array(' Search index for ' . $objectType->objectType));
-               
-               // log table
-               $sql = "INSERT INTO     wcf".WCF_N."_package_installation_sql_log
-                                       (packageID, sqlTable)
-                       VALUES          (?, ?)";
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute(array(
-                       $objectType->packageID,
-                       $tableName
-               ));
-               
-               return true;
+       public function createSearchIndices() {
+               $this->getSearchIndexManager()->createSearchIndices();
        }
        
        /**
diff --git a/wcfsetup/install/files/lib/system/search/mysql/MysqlSearchEngine.class.php b/wcfsetup/install/files/lib/system/search/mysql/MysqlSearchEngine.class.php
new file mode 100644 (file)
index 0000000..6c15b3e
--- /dev/null
@@ -0,0 +1,244 @@
+<?php
+namespace wcf\system\search\mysql;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\database\DatabaseException;
+use wcf\system\exception\SystemException;
+use wcf\system\search\AbstractSearchEngine;
+use wcf\system\search\SearchIndexManager;
+use wcf\system\WCF;
+use wcf\util\StringUtil;
+use wcf\system\search\SearchEngine;
+
+/**
+ * Search engine using MySQL's FULLTEXT index.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+class MysqlSearchEngine extends AbstractSearchEngine {
+       /**
+        * MySQL's minimum word length for fulltext indices
+        * @var integer
+        */
+       protected static $ftMinWordLen = null;
+       
+       /**
+        * @see \wcf\system\search\ISearchEngine::search()
+        */
+       public function search($q, array $objectTypes, $subjectOnly = false, PreparedStatementConditionBuilder $searchIndexCondition = null, array $additionalConditions = array(), $orderBy = 'time DESC', $limit = 1000) {
+               // handle sql types
+               $fulltextCondition = null;
+               $relevanceCalc = '';
+               if (!empty($q)) {
+                       $q = $this->parseSearchQuery($q);
+                       
+                       $fulltextCondition = new PreparedStatementConditionBuilder(false);
+                       switch (WCF::getDB()->getDBType()) {
+                               case 'wcf\system\database\MySQLDatabase':
+                                       $fulltextCondition->add("MATCH (subject".(!$subjectOnly ? ', message, metaData' : '').") AGAINST (? IN BOOLEAN MODE)", array($q));
+                               break;
+                               
+                               case 'wcf\system\database\PostgreSQLDatabase':
+                                       // replace * with :*
+                                       $q = str_replace('*', ':*', $q);
+                                       
+                                       $fulltextCondition->add("fulltextIndex".($subjectOnly ? "SubjectOnly" : '')." @@ to_tsquery(?)", array($q));
+                               break;
+                               
+                               default:
+                                       throw new SystemException("your database type doesn't support fulltext search");
+                               break;
+                       }
+                       
+                       if ($orderBy == 'relevance ASC' || $orderBy == 'relevance DESC') {
+                               switch (WCF::getDB()->getDBType()) {
+                                       case 'wcf\system\database\MySQLDatabase':
+                                               $relevanceCalc = "MATCH (subject".(!$subjectOnly ? ', message, metaData' : '').") AGAINST ('".escapeString($q)."') + (5 / (1 + POW(LN(1 + (".TIME_NOW." - time) / 2592000), 2))) AS relevance";
+                                       break;
+                                       
+                                       case 'wcf\system\database\PostgreSQLDatabase':
+                                               $relevanceCalc = "ts_rank_cd(fulltextIndex".($subjectOnly ? "SubjectOnly" : '').", '".escapeString($q)."') AS relevance";
+                                       break;
+                               }
+                       }
+               }
+               
+               // build search query
+               $sql = '';
+               $parameters = array();
+               foreach ($objectTypes as $objectTypeName) {
+                       $objectType = SearchEngine::getInstance()->getObjectType($objectTypeName);
+                       if (!empty($sql)) $sql .= "\nUNION\n";
+                       $additionalConditionsConditionBuilder = (isset($additionalConditions[$objectTypeName]) ? $additionalConditions[$objectTypeName] : null);
+                       if (($specialSQL = $objectType->getSpecialSQLQuery($fulltextCondition, $searchIndexCondition, $additionalConditionsConditionBuilder, $orderBy))) {
+                               $sql .= "(".$specialSQL.")";
+                       }
+                       else {
+                               $sql .= "(
+                                               SELECT          ".$objectType->getIDFieldName()." AS objectID,
+                                                               ".$objectType->getSubjectFieldName()." AS subject,
+                                                               ".$objectType->getTimeFieldName()." AS time,
+                                                               ".$objectType->getUsernameFieldName()." AS username,
+                                                               '".$objectTypeName."' AS objectType
+                                                               ".($relevanceCalc ? ',search_index.relevance' : '')."
+                                               FROM            ".$objectType->getTableName()."
+                                               INNER JOIN      (
+                                                                       SELECT          objectID
+                                                                                       ".($relevanceCalc ? ','.$relevanceCalc : '')."
+                                                                       FROM            ".SearchIndexManager::getTableName($objectTypeName)."
+                                                                       WHERE           ".($fulltextCondition !== null ? $fulltextCondition : '')."
+                                                                                       ".(($searchIndexCondition !== null && $searchIndexCondition->__toString()) ? ($fulltextCondition !== null ? "AND " : '').$searchIndexCondition : '')."
+                                                                       ".(!empty($orderBy) && $fulltextCondition === null ? 'ORDER BY '.$orderBy : '')."
+                                                                       LIMIT           1000
+                                                               ) search_index
+                                               ON              (".$objectType->getIDFieldName()." = search_index.objectID)
+                                               ".$objectType->getJoins()."
+                                               ".(isset($additionalConditions[$objectTypeName]) ? $additionalConditions[$objectTypeName] : '')."
+                                       )";
+                       }
+                       
+                       if ($fulltextCondition !== null) $parameters = array_merge($parameters, $fulltextCondition->getParameters());
+                       if ($searchIndexCondition !== null) $parameters = array_merge($parameters, $searchIndexCondition->getParameters());
+                       if (isset($additionalConditions[$objectTypeName])) $parameters = array_merge($parameters, $additionalConditions[$objectTypeName]->getParameters());
+               }
+               if (empty($sql)) {
+                       throw new SystemException('no object types given');
+               }
+               
+               if (!empty($orderBy)) {
+                       $sql .= " ORDER BY " . $orderBy;
+               }
+               
+               // send search query
+               $messages = array();
+               $statement = WCF::getDB()->prepareStatement($sql, $limit);
+               $statement->execute($parameters);
+               while ($row = $statement->fetchArray()) {
+                       $messages[] = array(
+                               'objectID' => $row['objectID'],
+                               'objectType' => $row['objectType']
+                       );
+               }
+               
+               return $messages;
+       }
+       
+       /**
+        * Manipulates the search term (< and > used as quotation marks):
+        * 
+        * - <test foo> becomes <+test* +foo*>
+        * - <test -foo bar> becomes <+test* -foo* +bar*>
+        * - <test "foo bar"> becomes <+test* +"foo bar">
+        * 
+        * @see http://dev.mysql.com/doc/refman/5.5/en/fulltext-boolean.html
+        * 
+        * @param       string          $query
+        */
+       protected function parseSearchQuery($query) {
+               $query = StringUtil::trim($query);
+               
+               // expand search terms with a * unless they're encapsulated with quotes
+               $inQuotes = false;
+               $previousChar = $tmp = '';
+               $controlCharacterOrSpace = false;
+               $chars = array('+', '-', '*');
+               $ftMinWordLen = self::getFulltextMinimumWordLength();
+               for ($i = 0, $length = mb_strlen($query); $i < $length; $i++) {
+                       $char = mb_substr($query, $i, 1);
+                       
+                       if ($inQuotes) {
+                               if ($char == '"') {
+                                       $inQuotes = false;
+                               }
+                       }
+                       else {
+                               if ($char == '"') {
+                                       $inQuotes = true;
+                               }
+                               else {
+                                       if ($char == ' ' && !$controlCharacterOrSpace) {
+                                               $controlCharacterOrSpace = true;
+                                               $tmp .= '*';
+                                       }
+                                       else if (in_array($char, $chars)) {
+                                               $controlCharacterOrSpace = true;
+                                       }
+                                       else {
+                                               $controlCharacterOrSpace = false;
+                                       }
+                               }
+                       }
+                       
+                       /*
+                        * prepend a plus sign (logical AND) if ALL these conditions are given:
+                        *
+                        * 1) previous character:
+                        *   - is empty (start of string)
+                        *   - is a space (MySQL uses spaces to separate words)
+                        *
+                        * 2) not within quotation marks
+                        *
+                        * 3) current char:
+                        *   - is NOT +, - or *
+                        */
+                       if (($previousChar == '' || $previousChar == ' ') && !$inQuotes && !in_array($char, $chars)) {
+                               // check if the term is shorter than MySQL's ft_min_word_len
+                               
+                               if ($i + $ftMinWordLen <= $length) {
+                                       $term = '';// $char;
+                                       for ($j = $i, $innerLength = $ftMinWordLen + $i; $j < $innerLength; $j++) {
+                                               $currentChar = mb_substr($query, $j, 1);
+                                               if ($currentChar == '"' || $currentChar == ' ' || in_array($currentChar, $chars)) {
+                                                       break;
+                                               }
+                                               
+                                               $term .= $currentChar;
+                                       }
+                                       
+                                       if (mb_strlen($term) == $ftMinWordLen) {
+                                               $tmp .= '+';
+                                       }
+                               }
+                       }
+                       
+                       $tmp .= $char;
+                       $previousChar = $char;
+               }
+               
+               // handle last char
+               if (!$inQuotes && !$controlCharacterOrSpace) {
+                       $tmp .= '*';
+               }
+               
+               return $tmp;
+       }
+       
+       /**
+        * Returns MySQL's minimum word length for fulltext indices.
+        * 
+        * @return      integer
+        */
+       protected static function getFulltextMinimumWordLength() {
+               if (self::$ftMinWordLen === null) {
+                       $sql = "SHOW VARIABLES LIKE ?";
+                       $statement = WCF::getDB()->prepareStatement($sql);
+                       
+                       try {
+                               $statement->execute(array('ft_min_word_len'));
+                               $row = $statement->fetchArray();
+                       }
+                       catch (DatabaseException $e) {
+                               // fallback if user is disallowed to issue 'SHOW VARIABLES'
+                               $row = array('Value' => 4);
+                       }
+                       
+                       self::$ftMinWordLen = $row['Value'];
+               }
+               
+               return self::$ftMinWordLen;
+       }
+}
diff --git a/wcfsetup/install/files/lib/system/search/mysql/MysqlSearchIndexManager.class.php b/wcfsetup/install/files/lib/system/search/mysql/MysqlSearchIndexManager.class.php
new file mode 100644 (file)
index 0000000..016ae22
--- /dev/null
@@ -0,0 +1,124 @@
+<?php
+namespace wcf\system\search\mysql;
+use wcf\data\object\type\ObjectType;
+use wcf\system\search\AbstractSearchIndexManager;
+use wcf\system\WCF;
+use wcf\system\search\SearchIndexManager;
+
+/**
+ * Search engine using MySQL's FULLTEXT index.
+ * 
+ * @author     Alexander Ebert
+ * @copyright  2001-2014 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
+ */
+class MysqlSearchIndexManager extends AbstractSearchIndexManager {
+       /**
+        * @see \wcf\system\search\ISearchIndexManager::add()
+        */
+       public function add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array()) {
+               if ($languageID === null) $languageID = 0;
+               
+               // save new entry
+               $sql = "REPLACE INTO    " . SearchIndexManager::getTableName($objectType) . "
+                                       (objectID, subject, message, time, userID, username, languageID, metaData)
+                       VALUES          (?, ?, ?, ?, ?, ?, ?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($objectID, $subject, $message, $time, $userID, $username, $languageID, $metaData));
+       }
+       
+       /**
+        * @see \wcf\system\search\ISearchIndexManager::update()
+        */
+       public function update($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID = null, $metaData = '', array $additionalData = array()) {
+               // delete existing entry
+               $this->delete($objectType, array($objectID));
+               
+               // save new entry
+               $this->add($objectType, $objectID, $message, $subject, $time, $userID, $username, $languageID, $metaData, $additionalData);
+       }
+       
+       /**
+        * @see \wcf\system\search\ISearchIndexManager::delete()
+        */
+       public function delete($objectType, array $objectIDs) {
+               $sql = "DELETE FROM     " . SearchIndexManager::getTableName($objectType) . "
+                       WHERE           objectID = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               WCF::getDB()->beginTransaction();
+               foreach ($objectIDs as $objectID) {
+                       $statement->execute(array($objectID));
+               }
+               WCF::getDB()->commitTransaction();
+       }
+       
+       /**
+        * @see \wcf\system\search\ISearchIndexManager::reset()
+        */
+       public function reset($objectType) {
+               $sql = "TRUNCATE TABLE " . SearchIndexManager::getTableName($objectType);
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute();
+       }
+       
+       /**
+        * @see \wcf\system\search\AbstractSearchIndexManager::createSearchIndex()
+        */
+       protected function createSearchIndex(ObjectType $objectType) {
+               $tableName = SearchIndexManager::getTableName($objectType);
+               
+               // check if table already exists
+               $sql = "SELECT  COUNT(*) AS count
+                       FROM    wcf".WCF_N."_package_installation_sql_log
+                       WHERE   sqlTable = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array($tableName));
+               $row = $statement->fetchArray();
+               if ($row['count']) {
+                       // table already exists
+                       return false;
+               }
+               
+               $columns = array(
+                       array('name' => 'objectID', 'data' => array('length' => 10, 'notNull' => true, 'type' => 'int')),
+                       array('name' => 'subject', 'data' => array('default' => '', 'length' => 255, 'notNull' => true, 'type' => 'varchar')),
+                       array('name' => 'message', 'data' => array('type' => 'mediumtext')),
+                       array('name' => 'metaData', 'data' => array('type' => 'mediumtext')),
+                       array('name' => 'time', 'data' => array('default' => 0, 'length' => 10, 'notNull' => true, 'type' => 'int')),
+                       array('name' => 'userID', 'data' => array('default' => '', 'length' => 10, 'type' => 'int')),
+                       array('name' => 'username', 'data' => array('default' => '', 'length' => 255,'notNull' => true, 'type' => 'varchar')),
+                       array('name' => 'languageID', 'data' => array('default' => 0, 'length' => 10, 'notNull' => true, 'type' => 'int'))
+               );
+               
+               $indices = array(
+                       array('name' => 'objectAndLanguage', 'data' => array('columns' => 'objectID, languageID', 'type' => 'UNIQUE')),
+                       array('name' => 'fulltextIndex', 'data' => array('columns' => 'subject, message, metaData', 'type' => 'FULLTEXT')),
+                       array('name' => 'fulltextIndexSubjectOnly', 'data' => array('columns' => 'subject', 'type' => 'FULLTEXT')),
+                       array('name' => 'language', 'data' => array('columns' => 'languageID', 'type' => 'KEY')),
+                       array('name' => 'user', 'data' => array('columns' => 'userID, time', 'type'=> 'KEY'))
+               );
+               
+               WCF::getDB()->getEditor()->createTable($tableName, $columns, $indices);
+               
+               // add comment
+               $sql = "ALTER TABLE     ".$tableName."
+                       COMMENT         = ?";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(' Search index for ' . $objectType->objectType));
+               
+               // log table
+               $sql = "INSERT INTO     wcf".WCF_N."_package_installation_sql_log
+                                       (packageID, sqlTable)
+                       VALUES          (?, ?)";
+               $statement = WCF::getDB()->prepareStatement($sql);
+               $statement->execute(array(
+                       $objectType->packageID,
+                       $tableName
+               ));
+               
+               return true;
+       }
+}
index 3f42323f7e76d4ff2c1fb0d4e565ef5cd588a1e7..b224f348121e3af627bf9c3d89af7bfbb65f79e7 100644 (file)
@@ -36,3 +36,4 @@ define('ENABLE_BENCHMARK', 0);
 define('EXTERNAL_LINK_TARGET_BLANK', 0);
 define('URL_LEGACY_MODE', 0);
 define('URL_TO_LOWERCASE', 1);
+define('SEARCH_ENGINE', 'mysql');