From: Alexander Ebert Date: Wed, 15 Aug 2012 17:48:58 +0000 (+0200) Subject: Added prototype for ACP search X-Git-Tag: 2.0.0_Beta_1~959^2~8 X-Git-Url: https://git.stricted.de/?a=commitdiff_plain;h=e9311b98a7b79640dd7a6a8c4fc6b8863606a39b;p=GitHub%2FWoltLab%2FWCF.git Added prototype for ACP search --- diff --git a/com.woltlab.wcf/acpSearchProvider.xml b/com.woltlab.wcf/acpSearchProvider.xml new file mode 100644 index 0000000000..7b86ff031f --- /dev/null +++ b/com.woltlab.wcf/acpSearchProvider.xml @@ -0,0 +1,13 @@ + + + + + + 1 + + + + 2 + + + diff --git a/com.woltlab.wcf/package.xml b/com.woltlab.wcf/package.xml index 9ada6fc3de..909be5e2aa 100644 --- a/com.woltlab.wcf/package.xml +++ b/com.woltlab.wcf/package.xml @@ -28,5 +28,6 @@ clipboardAction.xml objectTypeDefinition.xml objectType.xml + acpSearchProvider.xml diff --git a/com.woltlab.wcf/packageInstallationPlugin.xml b/com.woltlab.wcf/packageInstallationPlugin.xml index ab7d051a16..3536981363 100644 --- a/com.woltlab.wcf/packageInstallationPlugin.xml +++ b/com.woltlab.wcf/packageInstallationPlugin.xml @@ -21,5 +21,6 @@ wcf\system\package\plugin\UserOptionPackageInstallationPlugin wcf\system\package\plugin\PackageIconPackageInstallationPlugin wcf\system\package\plugin\SitemapPackageInstallationPlugin + wcf\system\package\plugin\ACPSearchProviderPackageInstallationPlugin diff --git a/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProvider.class.php b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProvider.class.php new file mode 100644 index 0000000000..86354db2c6 --- /dev/null +++ b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProvider.class.php @@ -0,0 +1,25 @@ + + * @package com.woltlab.wcf + * @subpackage data.acp.search.provider + * @category Community Framework + */ +class ACPSearchProvider extends DatabaseObject { + /** + * @see wcf\data\DatabaseObject::$databaseTableName + */ + protected static $databaseTableName = 'acp_search_provider'; + + /** + * @see wcf\data\DatabaseObject::$databaseTableIndexName + */ + protected static $databaseTableIndexName = 'providerID'; +} diff --git a/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderAction.class.php b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderAction.class.php new file mode 100644 index 0000000000..a4ff2fa9b9 --- /dev/null +++ b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderAction.class.php @@ -0,0 +1,15 @@ + + * @package com.woltlab.wcf + * @subpackage data.acp.search.provider + * @category Community Framework + */ +class ACPSearchProviderAction extends AbstractDatabaseObjectAction { } diff --git a/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderEditor.class.php b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderEditor.class.php new file mode 100644 index 0000000000..0c2381154b --- /dev/null +++ b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderEditor.class.php @@ -0,0 +1,20 @@ + + * @package com.woltlab.wcf + * @subpackage data.acp.search.provider + * @category Community Framework + */ +class ACPSearchProviderEditor extends DatabaseObjectEditor { + /** + * @see wcf\data\DatabaseObjectDecorator::$baseClass + */ + protected static $baseClass = 'wcf\data\acp\search\provider\ACPSearchProvider'; +} diff --git a/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderList.class.php b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderList.class.php new file mode 100644 index 0000000000..bb9fdaed7c --- /dev/null +++ b/wcfsetup/install/files/lib/data/acp/search/provider/ACPSearchProviderList.class.php @@ -0,0 +1,15 @@ + + * @package com.woltlab.wcf + * @subpackage data.acp.search.provider + * @category Community Framework + */ +class ACPSearchProviderList extends DatabaseObjectList { } diff --git a/wcfsetup/install/files/lib/system/cache/builder/ACPSearchProviderCacheBuilder.class.php b/wcfsetup/install/files/lib/system/cache/builder/ACPSearchProviderCacheBuilder.class.php new file mode 100644 index 0000000000..a2d180245f --- /dev/null +++ b/wcfsetup/install/files/lib/system/cache/builder/ACPSearchProviderCacheBuilder.class.php @@ -0,0 +1,31 @@ + + * @package com.woltlab.wcf + * @subpackage system.cache.builder + * @category Community Framework + */ +class ACPSearchProviderCacheBuilder implements ICacheBuilder { + /** + * @see wcf\system\cache\ICacheBuilder::getData() + */ + public function getData(array $cacheResource) { + list($cache, $packageID) = explode('-', $cacheResource['cache']); + + $providerList = new ACPSearchProviderList(); + $providerList->getConditionBuilder()->add("acp_search_provider.packageID IN (?)", array(PackageDependencyHandler::getInstance()->getDependencies())); + $providerList->sqlLimit = 0; + $providerList->sqlOrderBy = "acp_search_provider.showOrder ASC"; + $providerList->readObjects(); + + return $providerList->getObjects(); + } +} diff --git a/wcfsetup/install/files/lib/system/package/plugin/ACPSearchProviderPackageInstallationPlugin.class.php b/wcfsetup/install/files/lib/system/package/plugin/ACPSearchProviderPackageInstallationPlugin.class.php new file mode 100644 index 0000000000..abafea3f14 --- /dev/null +++ b/wcfsetup/install/files/lib/system/package/plugin/ACPSearchProviderPackageInstallationPlugin.class.php @@ -0,0 +1,98 @@ + + * @package com.woltlab.wcf + * @subpackage system.package.plugin + * @category Community Framework + */ +class ACPSearchProviderPackageInstallationPlugin extends AbstractXMLPackageInstallationPlugin { + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$className + */ + public $className = 'wcf\data\acp\search\provider\ACPSearchProviderEditor'; + + /** + * @see wcf\system\package\plugin\AbstractPackageInstallationPlugin::$tableName + */ + public $tableName = 'acp_search_provider'; + + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::$tagName + */ + public $tagName = 'acpsearchprovider'; + + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::handleDelete() + */ + protected function handleDelete(array $items) { + $sql = "DELETE FROM wcf".WCF_N."_".$this->tableName." + WHERE providerName = ? + AND packageID = ?"; + $statement = WCF::getDB()->prepareStatement($sql); + foreach ($items as $item) { + $statement->execute(array( + $item['attributes']['name'], + $this->installation->getPackageID() + )); + } + } + + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::prepareImport() + */ + protected function prepareImport(array $data) { + // get show order + $showOrder = (isset($data['elements']['showorder'])) ? $data['elements']['showorder'] : null; + $showOrder = $this->getShowOrder($showOrder); + + return array( + 'className' => $data['elements']['classname'], + 'providerName' => $data['attributes']['name'], + 'showOrder' => $showOrder + ); + } + + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::findExistingItem() + */ + protected function findExistingItem(array $data) { + $sql = "SELECT * + FROM wcf".WCF_N."_".$this->tableName." + WHERE providerName = ? + AND packageID = ?"; + $parameters = array( + $data['providerName'], + $this->installation->getPackageID() + ); + + return array( + 'sql' => $sql, + 'parameters' => $parameters + ); + } + + /** + * @see wcf\system\package\plugin\AbstractXMLPackageInstallationPlugin::cleanup() + */ + protected function cleanup() { + CacheHandler::getInstance()->clear(WCF_DIR.'cache', 'cache.acpSearchProvider-*.php'); + } + + /** + * @see wcf\system\package\plugin\IPackageInstallationPlugin::uninstall() + */ + public function uninstall() { + parent::uninstall(); + + // clear cache immediately + CacheHandler::getInstance()->clear(WCF_DIR.'cache', 'cache.acpSearchProvider-*.php'); + } +} diff --git a/wcfsetup/install/files/lib/system/search/acp/ACPSearchHandler.class.php b/wcfsetup/install/files/lib/system/search/acp/ACPSearchHandler.class.php new file mode 100644 index 0000000000..0fad9207a4 --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/ACPSearchHandler.class.php @@ -0,0 +1,104 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +class ACPSearchHandler extends SingletonFactory { + /** + * list of acp search provider + * @var array + */ + protected $cache = null; + + /** + * @see wcf\system\SingletonFactory::init() + */ + protected function init() { + $application = ApplicationHandler::getInstance()->getPrimaryApplication(); + $cacheName = 'acpSearchProvider-'.$application->packageID; + + CacheHandler::getInstance()->addResource( + $cacheName, + WCF_DIR.'cache/cache.'.$cacheName.'.php', + 'wcf\system\cache\builder\ACPSearchProviderCacheBuilder' + ); + + $this->cache = CacheHandler::getInstance()->get($cacheName); + } + + /** + * Returns a list of search result collections for given query. + * + * @param string $query + * @param integer $limit + * @return array + */ + public function search($query, $limit = 10) { + $data = array(); + $maxResultsPerProvider = ceil($limit / 2); + $totalResultCount = 0; + + foreach ($this->cache as $acpSearchProvider) { + $className = $acpSearchProvider->className; + if (!ClassUtil::isInstanceOf($className, 'wcf\system\search\acp\IACPSearchProvider')) { + throw new SystemException("Class '".$className."' does not implement the interface 'wcf\system\search\acp\IACPSearchProvider'"); + } + + $provider = new $className(); + $results = $provider->search($query, $maxResultsPerProvider); + + if (!empty($results)) { + $resultList = new ACPSearchResultList(); + foreach ($results as $result) { + $resultList->addResult($result); + } + + $data[] = $resultList; + $totalResultCount += count($resultList); + } + } + + // reduce results per collection until we match the limit + while ($totalResultCount > $limit) { + // calculate highest value + $max = 0; + foreach ($data as $resultList) { + $max = max($max, count($resultList)); + } + + // remove one result per result list with hits the $max value + foreach ($data as $index => $resultList) { + // break if we hit the $limit during reduction + if ($totalResultCount == $limit) { + break; + } + + $count = count($resultList); + if ($count == $max) { + $resultList->reduceResults(1); + $totalResultCount--; + + // the last element of this result was removed + if ($count == 1) { + unset($data[$index]); + } + } + } + } + + return $data; + } +} diff --git a/wcfsetup/install/files/lib/system/search/acp/ACPSearchResult.class.php b/wcfsetup/install/files/lib/system/search/acp/ACPSearchResult.class.php new file mode 100644 index 0000000000..1ed809fdbf --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/ACPSearchResult.class.php @@ -0,0 +1,62 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +class ACPSearchResult { + /** + * item link + * @var string + */ + protected $link = ''; + + /** + * item title + * @var string + */ + protected $title = ''; + + /** + * Creates a new ACP search result. + * + * @param string $title + * @param string $link + */ + public function __construct($title, $link) { + $this->title = $title; + $this->link = $link; + } + + /** + * Returns the item link. + * + * @return string + */ + public function getLink() { + return $this->link; + } + + /** + * Returns the item title. + * + * @return string + */ + public function getTitle() { + return $this->title; + } + + /** + * @see wcf\system\search\acp\ACPSearchResult::getTitle() + */ + public function __toString() { + return $this->getTitle(); + } +} diff --git a/wcfsetup/install/files/lib/system/search/acp/ACPSearchResultList.class.php b/wcfsetup/install/files/lib/system/search/acp/ACPSearchResultList.class.php new file mode 100644 index 0000000000..915199dad3 --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/ACPSearchResultList.class.php @@ -0,0 +1,108 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +class ACPSearchResultList implements \Countable, \Iterator { + /** + * current iterator index + * @var integer + */ + protected $index = 0; + + /** + * result list + * @var array + */ + protected $results = array(); + + /** + * Adds a result to the collection. + * + * @param wcf\system\search\acp\ACPSearchResult $result + */ + public function addResult(ACPSearchResult $result) { + $this->results[] = $result; + } + + /** + * Reduces the result collection by given count. If the count is higher + * than the actual amount of results, the results will be cleared. + * + * @param integer $count + */ + public function reduceResults($count) { + // more results than available should be whiped, just set it to 0 + if ($count >= count($this->results)) { + $this->results = array(); + } + else { + while ($count > 0) { + array_pop($this->results); + $count--; + } + } + + // rewind index to prevent bad offsets + $this->rewind(); + } + + /** + * Sorts results by title. + */ + public function sort() { + usort($this->results, function($a, $b) { + return strcmp($a->getTitle(), $b->getTitle()); + }); + } + + /** + * @see \Countable::count() + */ + public function count() { + return count($this->objects); + } + + /** + * @see \Iterator::current() + */ + public function current() { + return $this->results[$this->index]; + } + + /** + * @see \Iterator::key() + */ + public function key() { + return $this->index; + } + + /** + * @see \Iterator::next() + */ + public function next() { + ++$this->index; + } + + /** + * @see \Iterator::rewind() + */ + public function rewind() { + $this->index = 0; + } + + /** + * @see \Iterator::valid() + */ + public function valid() { + return isset($this->results[$this->index]); + } +} diff --git a/wcfsetup/install/files/lib/system/search/acp/IACPSearchResultProvider.class.php b/wcfsetup/install/files/lib/system/search/acp/IACPSearchResultProvider.class.php new file mode 100644 index 0000000000..8fc375b8c5 --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/IACPSearchResultProvider.class.php @@ -0,0 +1,23 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +interface IACPSearchResultProvider { + /** + * Returns a list of seach results for given query. + * + * @param string $query + * @param integer $limit + * @return array + */ + public function search($query, $limit = 5); +} diff --git a/wcfsetup/install/files/lib/system/search/acp/MenuItemACPSearchResultProvider.class.php b/wcfsetup/install/files/lib/system/search/acp/MenuItemACPSearchResultProvider.class.php new file mode 100644 index 0000000000..3d2b1f9c8d --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/MenuItemACPSearchResultProvider.class.php @@ -0,0 +1,81 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +class MenuItemACPSearchResultProvider implements IACPSearchResultProvider { + /** + * @see wcf\system\search\acp\IACPSearchResultProvider::search() + */ + public function search($query, $limit = 5) { + $results = array(); + + // search by language item + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("languageID = ?", array(WCF::getLanguage()->languageID)); + $conditions->add("languageItem LIKE ?", array('wcf.acp.option.'.$query.'%')); + $conditions->add("packageID IN (?)", array(PackageDependencyHandler::getInstance()->getDependencies())); + + // get available abbrevations + $packageIDs = array(ApplicationHandler::getInstance()->getActiveApplication()->packageID); + foreach (ApplicationHandler::getInstance()->getDependentApplications() as $application) { + $packageIDs[] = $application->packageID; + } + + $searchConditions = array(); + $searchString = ''; + foreach ($packageIDs as $packageID) { + if (!empty($searchString)) { + $searchString .= " OR "; + } + + $searchString .= "languageItem LIKE ?"; + $searchConditions[] = ApplicationHandler::getInstance()->getAbbrevation($packageID) . '.acp.menu.link.'.$query.'%'; + } + $conditions->add($searchString, $searchConditions); + + $sql = "SELECT languageItem, languageItemValue + FROM wcf".WCF_N."_language_item + ".$conditions." + ORDER BY languageItemValue ASC"; + $statement = WCF::getDB()->prepareStatement($sql, ($limit * 2)); // use double limit here since categories are matched too + $statement->execute($conditions->getParameters()); + $languageItems = array(); + while ($row = $statement->fetchArray()) { + $languageItems[$row['languageItem']] = $row['languageItemValue']; + } + + if (empty($languageItems)) { + return array(); + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("menuItem IN (?)", array(array_keys($languageItems))); + $conditions->add("menuItemLink <> ''"); + + $sql = "SELECT menuItem, menuItemLink + FROM wcf".WCF_N."_acp_menu_item + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql, $limit); + $statement->execute($conditions->getParameters()); + + while ($row = $statement->fetchArray()) { + $results[] = new ACPSearchResult($languageItems[$row['menuItem']], $row['menuItemLink']); + } + + return $results; + } +} diff --git a/wcfsetup/install/files/lib/system/search/acp/OptionACPSearchResultProvider.class.php b/wcfsetup/install/files/lib/system/search/acp/OptionACPSearchResultProvider.class.php new file mode 100644 index 0000000000..f56aa02e3c --- /dev/null +++ b/wcfsetup/install/files/lib/system/search/acp/OptionACPSearchResultProvider.class.php @@ -0,0 +1,68 @@ + + * @package com.woltlab.wcf + * @subpackage system.search.acp + * @category Community Framework + */ +class OptionACPSearchResultProvider implements IACPSearchResultProvider { + /** + * @see wcf\system\search\acp\IACPSearchResultProvider::search() + */ + public function search($query, $limit = 5) { + $results = array(); + + // search by language item + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("languageID = ?", array(WCF::getLanguage()->languageID)); + $conditions->add("languageItem LIKE ?", array('wcf.acp.option.'.$query.'%')); + $conditions->add("packageID IN (?)", array(PackageDependencyHandler::getInstance()->getDependencies())); + + $sql = "SELECT languageItem, languageItemValue + FROM wcf".WCF_N."_language_item + ".$conditions." + ORDER BY languageItemValue ASC"; + $statement = WCF::getDB()->prepareStatement($sql, $limit); + $statement->execute($conditions->getParameters()); + $languageItems = array(); + $optionNames = array(); + while ($row = $statement->fetchArray()) { + $optionName = str_replace('wcf.acp.option.', '', $row['languageItem']); + + $languageItems[$optionName] = $row['languageItemValue']; + $optionNames[] = $optionName; + } + + if (empty($optionNames)) { + return array(); + } + + $conditions = new PreparedStatementConditionBuilder(); + $conditions->add("option.optionName IN (?)", array($optionNames)); + + $sql = "SELECT option.optionName, option.categoryName, option_category.categoryID + FROM wcf".WCF_N."_option option + LEFT JOIN wcf".WCF_N."_option_category option_category + ON (option_category.categoryName = option.categoryName) + ".$conditions; + $statement = WCF::getDB()->prepareStatement($sql); + $statement->execute($conditions->getParameters()); + + while ($row = $statement->fetchArray()) { + $link = LinkHandler::getInstance()->getLink('Option', array('id' => $row['categoryID']), '#'.$row['categoryName']); + $results[] = new ACPSearchResult($languageItems[$row['optionName']], $link); + } + + return $results; + } +} diff --git a/wcfsetup/setup/db/install.sql b/wcfsetup/setup/db/install.sql index c6efbd941e..909c293bb9 100644 --- a/wcfsetup/setup/db/install.sql +++ b/wcfsetup/setup/db/install.sql @@ -12,6 +12,16 @@ CREATE TABLE wcf1_acp_menu_item ( UNIQUE KEY menuItem (menuItem, packageID) ); +DROP TABLE IF EXISTS wcf1_acp_search_provider; +CREATE TABLE wcf1_acp_search_provider ( + providerID INT(10) NOT NULL AUTO_INCREMENT PRIMARY KEY, + packageID INT(10) NOT NULL, + providerName VARCHAR(255) NOT NULL DEFAULT '', + className VARCHAR(255) NOT NULL DEFAULT '', + showOrder INT(10) NOT NULL DEFAULT 0, + UNIQUE KEY providerName (providerName, packageID) +); + DROP TABLE IF EXISTS wcf1_acp_session; CREATE TABLE wcf1_acp_session ( sessionID CHAR(40) NOT NULL PRIMARY KEY, @@ -767,6 +777,8 @@ CREATE TABLE wcf1_user_to_language ( /**** foreign keys ****/ ALTER TABLE wcf1_acp_menu_item ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE; +ALTER TABLE wcf1_acp_search_provider ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE; + ALTER TABLE wcf1_acp_session ADD FOREIGN KEY (userID) REFERENCES wcf1_user (userID) ON DELETE CASCADE; ALTER TABLE wcf1_acp_session ADD FOREIGN KEY (packageID) REFERENCES wcf1_package (packageID) ON DELETE CASCADE;