--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<data xmlns="http://www.woltlab.com" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.woltlab.com http://www.woltlab.com/XSD/acpSearchProvider.xsd">
+ <import>
+ <acpsearchprovider name="menuItem">
+ <classname><![CDATA[wcf\system\search\acp\MenuItemACPSearchResultProvider]]></classname>
+ <showorder>1</showorder>
+ </acpsearchprovider>
+ <acpsearchprovider name="option">
+ <classname><![CDATA[wcf\system\search\acp\OptionACPSearchResultProvider]]></classname>
+ <showorder>2</showorder>
+ </acpsearchprovider>
+ </import>
+</data>
<instruction type="clipboardAction">clipboardAction.xml</instruction>
<instruction type="objectTypeDefinition">objectTypeDefinition.xml</instruction>
<instruction type="objectType">objectType.xml</instruction>
+ <instruction type="acpSearchProvider">acpSearchProvider.xml</instruction>
</instructions>
</package>
<pip name="userOption">wcf\system\package\plugin\UserOptionPackageInstallationPlugin</pip>
<pip name="packageIcon">wcf\system\package\plugin\PackageIconPackageInstallationPlugin</pip>
<pip name="sitemap">wcf\system\package\plugin\SitemapPackageInstallationPlugin</pip>
+ <pip name="acpSearchProvider">wcf\system\package\plugin\ACPSearchProviderPackageInstallationPlugin</pip>
</import>
</data>
--- /dev/null
+<?php
+namespace wcf\data\acp\search\provider;
+use wcf\data\DatabaseObject;
+
+/**
+ * Represents an ACP search provider.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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';
+}
--- /dev/null
+<?php
+namespace wcf\data\acp\search\provider;
+use wcf\data\AbstractDatabaseObjectAction;
+
+/**
+ * Executes ACP search provider-related actions.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acp.search.provider
+ * @category Community Framework
+ */
+class ACPSearchProviderAction extends AbstractDatabaseObjectAction { }
--- /dev/null
+<?php
+namespace wcf\data\acp\search\provider;
+use wcf\data\DatabaseObjectEditor;
+
+/**
+ * Provides functions to edit acp search providers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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';
+}
--- /dev/null
+<?php
+namespace wcf\data\acp\search\provider;
+use wcf\data\DatabaseObjectList;
+
+/**
+ * Represents a list of ACP search providers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage data.acp.search.provider
+ * @category Community Framework
+ */
+class ACPSearchProviderList extends DatabaseObjectList { }
--- /dev/null
+<?php
+namespace wcf\system\cache\builder;
+use wcf\data\acp\search\provider\ACPSearchProviderList;
+use wcf\system\package\PackageDependencyHandler;
+
+/**
+ * Caches the ACP search providers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\package\plugin;
+use wcf\system\cache\CacheHandler;
+use wcf\system\WCF;
+
+/**
+ * This PIP installs, updates or deletes ACP search providers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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');
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\cache\CacheHandler;
+use wcf\system\exception\SystemException;
+use wcf\system\SingletonFactory;
+use wcf\util\ClassUtil;
+
+/**
+ * Handles ACP Search.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package com.woltlab.wcf
+ * @subpackage system.search.acp
+ * @category Community Framework
+ */
+class ACPSearchHandler extends SingletonFactory {
+ /**
+ * list of acp search provider
+ * @var array<wcf\data\acp\search\provider\ACPSearchProvider>
+ */
+ 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<wcf\system\search\acp\ACPSearchResultList>
+ */
+ 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;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+
+/**
+ * Represents an ACP search result.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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();
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+
+/**
+ * Represents a list of ACP search results.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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<wcf\system\search\acp\ACPSearchResult>
+ */
+ 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]);
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+
+/**
+ * Default implementation for ACP search providers.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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<wcf\system\search\acp\ACPSearchResult>
+ */
+ public function search($query, $limit = 5);
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+use wcf\system\application\ApplicationHandler;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\package\PackageDependencyHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * ACP search provider for menu items.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+ }
+}
--- /dev/null
+<?php
+namespace wcf\system\search\acp;
+use wcf\system\database\util\PreparedStatementConditionBuilder;
+use wcf\system\package\PackageDependencyHandler;
+use wcf\system\request\LinkHandler;
+use wcf\system\WCF;
+
+/**
+ * ACP search provider for options.
+ *
+ * @author Alexander Ebert
+ * @copyright 2001-2012 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @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;
+ }
+}
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,
/**** 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;