diff --git a/endpoints/search/search.php b/endpoints/search/search.php new file mode 100644 index 00000000..dc2687cd --- /dev/null +++ b/endpoints/search/search.php @@ -0,0 +1,113 @@ + Templated Page /w Listviews +*/ + + +class SearchBaseResponse extends TemplateResponse implements ICache +{ + use TrCache, TrSearch; + + private const SEARCH_MODS_ALL = 0x0FFFFFFF; // yeah im lazy, now what? + + protected int $cacheType = CACHE_TYPE_SEARCH; + + protected string $template = 'search'; + protected string $pageName = 'search'; + protected ?int $activeTab = parent::TAB_DATABASE; + + protected array $scripts = [[SC_JS_FILE, 'js/swfobject.js']]; + protected array $expectedGET = array( + 'search' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + public string $invalidTerms = ''; + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); // just to set g_user and g_locale + + $this->query = $this->_get['search']; // technically pageParam, but prepared + + if ($limit = Cfg::get('SQL_LIMIT_SEARCH')) + $this->maxResults = $limit; + + $this->searchMask = Search::TYPE_REGULAR | self::SEARCH_MODS_ALL; + + $this->searchObj = new Search($this->query, $this->searchMask, $this->maxResults); + } + + protected function generate() : void + { + if (!$this->query) // empty search > goto home page + $this->forward(); + + $this->search = $this->query; // escaped by TemplateResponse + + if ($iv = $this->searchObj->invalid) + $this->invalidTerms = implode(', ', Util::htmlEscape($iv)); + + array_unshift($this->title, $this->search, Lang::main('search')); + + $this->redButtons[BUTTON_WOWHEAD] = true; + $this->wowheadLink = sprintf(WOWHEAD_LINK, Lang::getLocale()->domain(), 'search=', Util::htmlEscape($this->query)); + + $this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"], __forceTabs: true); + + $canRedirect = true; + $redirectTo = ''; + foreach ($this->searchObj->perform() as $lvData) + { + if ($lvData[1] == 'npc' || $lvData[1] == 'object') + $this->addDataLoader('zones'); + + $this->lvTabs->addListviewTab(new Listview(...$lvData)); + + // we already have a target > can't have more targets > no redirects + if ($canRedirect && $redirectTo) + $canRedirect = false; + + if ($canRedirect) // note - we are very lucky that in case of searches $template is identical to the typeString + $redirectTo = '?'.$lvData[1].'='.key($lvData[0]['data']); + } + + $this->extendGlobalData($this->searchObj->getJSGlobals()); + + parent::generate(); + + $this->result->registerDisplayHook('lvTabs', [self::class, 'tabsHook']); + + // this one stings.. + // we have to manually call saveCache, beacause normally it would be called AFTER the page is rendered.. + // .. which will not happen if we forward to somewhere + // also we have to set a postCacheHook in this case that handles future forwards (gets called in display() so the currenct call is also covered) + if ($canRedirect && $redirectTo) + { + $this->setOnCacheLoaded([self::class, 'onBeforeDisplay'], $redirectTo); + $this->saveCache($this->result); + } + } + + // update dates to now() + public static function tabsHook(Template\PageTemplate $pt, Tabs &$lvTabs) : void + { + foreach ($lvTabs->iterate() as &$listview) + if ($listview instanceof Listview && $listview->getTemplate() == 'holiday') + WorldEventList::updateListview($listview); + } + + public static function onBeforeDisplay(Template\PageTemplate $pt, string $url) : never + { + header('Location: '.$url, true, 302); // we no longer have access to BaseResponse .. so thats fun + exit(); + } +} + +?> diff --git a/endpoints/search/search_json.php b/endpoints/search/search_json.php new file mode 100644 index 00000000..61c6f754 --- /dev/null +++ b/endpoints/search/search_json.php @@ -0,0 +1,94 @@ + search by compare or profiler (only items + itemsets) + array:[ + searchString, + [itemData], + [itemsetData] + ] +*/ + + +class SearchJsonResponse extends TextResponse implements ICache +{ + use TrCache, TrSearch; + + protected int $cacheType = CACHE_TYPE_SEARCH; + + protected array $expectedGET = array( + 'search' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine'] ], + 'wt' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIntArray'] ], + 'wtv' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIntArray'] ], + 'slots' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkIntArray'] ], + 'type' => ['filter' => FILTER_VALIDATE_INT, 'options' => ['min_range' => Type::ITEM, 'max_range' => Type::ITEMSET]] + ); + + private array $extraOpts = []; // for weighted search + private array $extraCnd = []; // for weighted search + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); + + $this->query = $this->_get['search']; // technically pageParam, but prepared + + if ($this->_get['wt'] && $this->_get['wtv']) // slots and type should get ignored + { + $itemFilter = new ItemListFilter($this->_get); + if ($_ = $itemFilter->createConditionsForWeights()) + { + $this->extraOpts = $itemFilter->extraOpts; + $this->extraCnd[] = $_; + } + } + + if ($_ = array_filter($this->_get['slots'] ?? [])) + $this->extraCnd[] = ['slot', $_]; + + if ($limit = Cfg::get('SQL_LIMIT_SEARCH')) + $this->maxResults = $limit; + + $this->searchMask = Search::TYPE_JSON; + if ($this->_get['slots'] || $this->_get['type'] == Type::ITEM) + $this->searchMask |= 1 << Search::MOD_ITEM; + else if ($this->_get['type'] == Type::ITEMSET) + $this->searchMask |= 1 << Search::MOD_ITEM | 1 << Search::MOD_ITEMSET; + + $this->searchObj = new Search($this->query, $this->searchMask, $this->maxResults, $this->extraCnd, $this->extraOpts); + } + + // !note! dear reader, if you ever try to generate a string, that is to be evaled by JS, NEVER EVER terminate with a \n ..... $totalHoursWasted +=2; + protected function generate() : void + { + $outItems = []; + $outSets = []; + + // invalid conditions: not enough characters to search OR no types to search + if (!$this->searchObj->canPerform()) + $this->generate404($this->query); + + foreach ($this->searchObj->perform() as $modId => $data) + { + if ($modId == Search::MOD_ITEM) + $outItems = $data; + else if ($modId == Search::MOD_ITEMSET) + $outSets = $data; + } + + $this->result = Util::toJSON([$this->query, $outItems, $outSets]); + } + + public function generate404(?string $search = ''): never + { + parent::generate404(Util::toJSON([$search, [], []])); + } +} + +?> diff --git a/endpoints/search/search_open.php b/endpoints/search/search_open.php new file mode 100644 index 00000000..ad154697 --- /dev/null +++ b/endpoints/search/search_open.php @@ -0,0 +1,129 @@ + suggestions when typing into searchboxes + array:[ + str, // search + str[10], // found + [], // unused - description for found result? + str[10], // url to found result + [], // unused + [], // unused + [], // unused + str[10][4] // type, typeId, param1 (4:quality, 3,6,9,10,17:icon, 5,11:faction), param2 (3:quality, 6:rank) + ] + + WH walked away from this hybrid approach and has separate endpoints for internal search suggestions and opensearch, with the latter only providing found text (index 1) + + we move the appendix of ' (TypeName)' on found text to descriptions as it fucks over Firefox users when they apply the suggestion +*/ + + +class SearchOpenResponse extends TextResponse implements ICache +{ + use TrCache, TrSearch; + + private const /* int */ SEARCH_MODS_OPEN = + 1 << Search::MOD_CLASS | 1 << Search::MOD_RACE | 1 << Search::MOD_TITLE | 1 << Search::MOD_WORLDEVENT | + 1 << Search::MOD_CURRENCY | 1 << Search::MOD_ITEMSET | 1 << Search::MOD_ITEM | 1 << Search::MOD_ABILITY | + 1 << Search::MOD_TALENT | 1 << Search::MOD_CREATURE | 1 << Search::MOD_QUEST | 1 << Search::MOD_ACHIEVEMENT | + 1 << Search::MOD_ZONE | 1 << Search::MOD_OBJECT | 1 << Search::MOD_FACTION | 1 << Search::MOD_SKILL | + 1 << Search::MOD_PET; + + protected string $contentType = MIME_TYPE_OPENSEARCH; + protected int $cacheType = CACHE_TYPE_SEARCH; + + protected array $expectedGET = array( + 'search' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextLine']] + ); + + public function __construct(string $pageParam) + { + parent::__construct($pageParam); // just to set g_user and g_locale + + $this->query = $this->_get['search']; // technically pageParam, but prepared + + if ($limit = Cfg::get('SQL_LIMIT_QUICKSEARCH')) + $this->maxResults = $limit; + + $this->searchMask = Search::TYPE_OPEN | self::SEARCH_MODS_OPEN; + + $this->searchObj = new Search($this->query, $this->searchMask, $this->maxResults); + } + + protected function generate() : void + { + // this one is funny: we want 10 results, ideally equally distributed over each type + $foundTotal = 0; + $result = array( // 0:query, 1:[names], 3:[links]; 7:[extraInfo] + $this->query, + [], [], [], [], [], [], [] + ); + + // invalid conditions: not enough characters to search OR no types to search + if (!$this->searchObj->canPerform()) + $this->generate404($this->query); + + foreach ($this->searchObj->perform() as [, , $nMatches, , , ]) + $foundTotal += $nMatches; + + foreach ($this->searchObj->perform() as [$data, $type, $nMatches, $param1, $param2, $desc]) + { + $max = max(1, intVal($this->maxResults * $nMatches / $foundTotal)); + + $i = 0; + foreach ($data as $id => $name) + { + if (++$i > $max) + break; + + if (count($result[1]) >= $this->maxResults) + break 2; + + $result[1][] = $name; // originally - $name . ' ('.$desc.')' + $result[2][] = $desc; // .. and here empty + $result[3][] = Cfg::get('HOST_URL').'/?'.Type::getFileString($type).'='.$id; + + $extra = [$type, $id]; // type, typeId + if (isset($param1[$id])) + $extra[] = $param1[$id]; // param1 + if (isset($param2[$id])) + $extra[] = $param2[$id]; // param2 + + $result[7][] = $extra; + } + } + + $this->result = Util::toJSON($result); + } + + public function generate404(?string $search = null) : never + { + parent::generate404(Util::toJSON([$search, [], [], [], [], [], [], []])); + } +} + +?> diff --git a/endpoints/searchbox/searchbox.php b/endpoints/searchbox/searchbox.php index a0575842..eda8f931 100644 --- a/endpoints/searchbox/searchbox.php +++ b/endpoints/searchbox/searchbox.php @@ -8,10 +8,10 @@ if (!defined('AOWOW_REVISION')) class SearchboxBaseResponse extends TemplateResponse { - protected string $template = 'text-page-generic'; - protected string $pageName = 'searchbox'; - protected ?int $activeTab = parent::TAB_MORE; - protected array $breadcrumb = [2, 16]; + protected string $template = 'text-page-generic'; + protected string $pageName = 'searchbox'; + protected ?int $activeTab = parent::TAB_MORE; + protected array $breadcrumb = [2, 16]; public function __construct(string $pageParam) { diff --git a/endpoints/searchplugins/searchplugins.php b/endpoints/searchplugins/searchplugins.php index 4a4d94c4..43925cce 100644 --- a/endpoints/searchplugins/searchplugins.php +++ b/endpoints/searchplugins/searchplugins.php @@ -8,10 +8,10 @@ if (!defined('AOWOW_REVISION')) class SearchpluginsBaseResponse extends TemplateResponse { - protected string $template = 'text-page-generic'; - protected string $pageName = 'searchplugins'; - protected ?int $activeTab = parent::TAB_MORE; - protected array $breadcrumb = [2, 8]; + protected string $template = 'text-page-generic'; + protected string $pageName = 'searchplugins'; + protected ?int $activeTab = parent::TAB_MORE; + protected array $breadcrumb = [2, 8]; public function __construct(string $pageParam) { diff --git a/includes/components/search.class.php b/includes/components/search.class.php new file mode 100644 index 00000000..734a45a6 --- /dev/null +++ b/includes/components/search.class.php @@ -0,0 +1,1588 @@ + '_searchCharClass', + self::MOD_RACE => '_searchCharRace', + self::MOD_TITLE => '_searchTitle', + self::MOD_WORLDEVENT => '_searchWorldEvent', + self::MOD_CURRENCY => '_searchCurrency', + self::MOD_ITEMSET => '_searchItemset', + self::MOD_ITEM => '_searchItem', + self::MOD_ABILITY => '_searchAbility', + self::MOD_TALENT => '_searchTalent', + self::MOD_GLYPH => '_searchGlyph', + self::MOD_PROFICIENCY => '_searchProficiency', + self::MOD_PROFESSION => '_searchProfession', + self::MOD_COMPANION => '_searchCompanion', + self::MOD_MOUNT => '_searchMount', + self::MOD_CREATURE => '_searchCreature', + self::MOD_QUEST => '_searchQuest', + self::MOD_ACHIEVEMENT => '_searchAchievement', + self::MOD_STATISTIC => '_searchStatistic', + self::MOD_ZONE => '_searchZone', + self::MOD_OBJECT => '_searchObject', + self::MOD_FACTION => '_searchFaction', + self::MOD_SKILL => '_searchSkill', + self::MOD_PET => '_searchPet', + self::MOD_CREATURE_ABILITY => '_searchCreatureAbility', + self::MOD_SPELL => '_searchSpell', + self::MOD_EMOTE => '_searchEmote', + self::MOD_ENCHANTMENT => '_searchEnchantment', + self::MOD_SOUND => '_searchSound' + ); + + private array $jsgStore = []; + private array $resultStore = []; + private array $included = []; + private array $excluded = []; + private array $cndBase = ['AND']; + private bool $idSearch = false; + + public array $invalid = []; + + public function __construct(private string $query, private int $moduleMask = -1, private int $maxResults = 500, private array $extraCnd = [], private array $extraOpts = []) + { + $this->tokenizeQuery(); + + $this->cndBase[] = $this->maxResults; + + // Exclude internal wow stuff + if (!User::isInGroup(U_GROUP_EMPLOYEE)) + $this->cndBase[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0]; + } + + private function tokenizeQuery() : void + { + if (!$this->query) + return; + + if (Util::checkNumeric($this->query, NUM_CAST_INT)) + { + $this->idSearch = true; + $this->included[] = $this->query; + return; + } + + foreach (explode(' ', $this->query) as $raw) + { + $clean = str_replace(['\\', '%'], '', $raw); + + if (!$clean === '') + continue; + + if ($clean[0] == '-') + { + if (mb_strlen($clean) < 4 && !Lang::getLocale()->isLogographic()) + $this->invalid[] = mb_substr($raw, 1); + else + $this->excluded[] = mb_substr(str_replace('_', '\\_', $clean), 1); + } + else + { + if (mb_strlen($clean) < 3 && !Lang::getLocale()->isLogographic()) + $this->invalid[] = $raw; + else + $this->included[] = str_replace('_', '\\_', $clean); + } + } + } + + private function createLookup(array $fields = []) : array + { + if ($this->idSearch && $this->included) + return ['id', $this->included]; + + if (!$this->included && !$this->excluded) + return []; + + // default to name-field + if (!$fields) + $fields[] = 'name_loc'.Lang::getLocale()->value; + + $qry = []; + foreach ($fields as $f) + { + $sub = []; + foreach ($this->included as $i) + $sub[] = [$f, '%'.$i.'%']; + + foreach ($this->excluded as $x) + $sub[] = [$f, '%'.$x.'%', '!']; + + // single cnd? + if (count($sub) > 1) + array_unshift($sub, 'AND'); + else + $sub = $sub[0]; + + $qry[] = $sub; + } + + // single cnd? + if (count($qry) > 1) + array_unshift($qry, 'OR'); + else + $qry = $qry[0]; + + return $qry; + } + + public function canPerform() : bool + { + // has valid search terms or weights and selected modules + return (($this->included || $this->extraOpts)) && $this->moduleMask; + } + + public function perform() : \Generator + { + $shared = []; + foreach (self::MODULES as $idx => $ref) + { + if (!($this->moduleMask & (1 << $idx))) + continue; + + $this->resultStore[$idx] ??= $this->$ref($shared); + + if (!$this->resultStore[$idx]) + continue; + + yield $idx => $this->resultStore[$idx]; + } + } + + public function getJSGlobals() : array + { + return $this->jsgStore; + } + + + /******************/ + /* Search Modules */ + /******************/ + + private function _searchCharClass() : ?array // 0 Classes: $moduleMask & 0x00000001 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $classes = new CharClassList($cnd, ['calcTotal' => true]); + + $data = $classes->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($classes->getMatches() > $this->maxResults) + { + // $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $classes->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, CharClassList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::CHR_CLASS, $classes->getMatches(), [], [], 'Class']; + + foreach ($classes->iterate() as $id => $__) + { + $result[$id] = $classes->getField('name', true); + $osInfo[2][$id] = 'class_'.strToLower($classes->getField('fileString')); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchCharRace() : ?array // 1 Races: $moduleMask & 0x00000002 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $races = new CharRaceList($cnd, ['calcTotal' => true]); + + $data = $races->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($races->getMatches() > $this->maxResults) + { + // $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $races->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, CharRaceList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::CHR_RACE, $races->getMatches(), [], [], 'Race']; + + foreach ($races->iterate() as $id => $__) + { + $result[$id] = $races->getField('name', true); + $osInfo[2][$id] = 'race_'.strToLower($races->getField('fileString')).'_male'; + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchTitle() : ?array // 2 Titles: $moduleMask & 0x00000004 + { + $cnd = array_merge($this->cndBase, [$this->createLookup(['male_loc'.Lang::getLocale()->value, 'female_loc'.Lang::getLocale()->value])]); + $titles = new TitleList($cnd, ['calcTotal' => true]); + + $data = $titles->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($titles->getMatches() > $this->maxResults) + { + // $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $titles->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, TitleList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::TITLE, $titles->getMatches(), [], [], 'Title']; + + foreach ($titles->iterate() as $id => $__) + { + $result[$id] = $titles->getField('male', true); + $osInfo[2][$id] = $titles->getField('side'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchWorldEvent() : ?array // 3 World Events: $moduleMask & 0x00000008 + { + $cnd = array_merge($this->cndBase, array( + array( + 'OR', + $this->createLookup(['h.name_loc'.Lang::getLocale()->value]), + ['AND', $this->createLookup(['e.description']), ['e.holidayId', 0]] + ) + )); + $wEvents = new WorldEventList($cnd, ['calcTotal' => true]); + + $data = $wEvents->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $wEvents->getJSGlobals()); + + // as allways: dates are updated in postCache-step + $lvData = ['data' => $data]; + + if ($wEvents->getMatches() > $this->maxResults) + { + // $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $wEvents->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, WorldEventList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::WORLDEVENT, $wEvents->getMatches(), [], [], 'World Event']; + + foreach ($wEvents->iterate() as $id => $__) + $result[$id] = $wEvents->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchCurrency() : ?array // 4 Currencies $moduleMask & 0x0000010 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $money = new CurrencyList($cnd, ['calcTotal' => true]); + + $data = $money->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($money->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_currenciesfound', $money->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, CurrencyList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::CURRENCY, $money->getMatches(), [], [], 'Currency']; + + foreach ($money->iterate() as $id => $__) + { + $result[$id] = $money->getField('name', true); + $osInfo[2][$id] = $money->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchItemset(array &$shared) : ?array// 5 Itemsets $moduleMask & 0x0000020 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $sets = new ItemsetList($cnd, ['calcTotal' => true]); + + $data = $sets->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $sets->getJSGlobals(GLOBALINFO_SELF)); + + $lvData = ['data' => $data]; + + if ($sets->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_itemsetsfound', $sets->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?itemsets&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?itemsets&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, ItemsetList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ITEMSET, $sets->getMatches(), [], [], 'Item Set']; + + foreach ($sets->iterate() as $id => $__) + { + $result[$id] = $sets->getField('name', true); + $osInfo[3][$id] = $sets->getField('quality'); + } + + return [$result, ...$osInfo]; + } + + if ($this->moduleMask & self::TYPE_JSON) + { + $shared['pcsToSet'] = $sets->pieceToSet; + + foreach ($data as &$d) + unset($d['quality'], $d['heroic']); + + return array_values($data); + } + + return null; + } + + private function _searchItem(array &$shared) : ?array // 6 Items $moduleMask & 0x0000040 + { + $miscData = ['calcTotal' => true]; + $lookup = $this->createLookup(); + + if ($this->moduleMask & self::TYPE_JSON) + { + if (!empty($shared['pcsToSet'])) + { + $cnd = [['i.id', array_keys($shared['pcsToSet'])], Cfg::get('SQL_LIMIT_NONE')]; + $miscData = ['pcsToSet' => $shared['pcsToSet']]; + } + else + { + $cnd = $this->cndBase; + $cnd[] = ['i.class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR]]; + $cnd[] = $lookup; + + if ($this->extraOpts) + $miscData['extraOpts'] = $this->extraOpts; + if ($this->extraCnd) + $cnd = array_merge($cnd, $this->extraCnd); + } + } + else + $cnd = array_merge($this->cndBase, [$lookup]); + + $items = new ItemList($cnd, $miscData); + + $data = $items->getListviewData($this->moduleMask & self::TYPE_JSON ? (ITEMINFO_SUBITEMS | ITEMINFO_JSON) : 0); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $items->getJSGlobals()); + + $lvData = ['data' => $data]; + + if ($items->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_itemsfound', $items->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?items&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?items&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, ItemList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ITEM, $items->getMatches(), [], [], 'Item']; + + foreach ($items->iterate() as $id => $__) + { + $result[$id] = $items->getField('name', true); + $osInfo[2][$id] = $items->getField('iconString'); + $osInfo[3][$id] = $items->getField('quality'); + } + + return [$result, ...$osInfo]; + } + + if ($this->moduleMask & self::TYPE_JSON) + { + foreach ($data as &$d) + if (!empty($d['subitems'])) + foreach ($d['subitems'] as &$si) + $si['enchantment'] = implode(', ', $si['enchantment']); + + return array_values($data); + } + + return null; + } + + private function _searchAbility() : ?array // 7 Abilities (Player + Pet) $moduleMask & 0x0000080 + { + $cnd = array_merge($this->cndBase, array( // hmm, inclued classMounts..? + ['s.typeCat', [7, -2, -3, -4]], + [['s.cuFlags', (SPELL_CU_TRIGGERED | SPELL_CU_TALENT), '&'], 0], + [['s.attributes0', 0x80, '&'], 0], + $this->createLookup() + )); + $abilities = new SpellList($cnd, ['calcTotal' => true]); + + $data = $abilities->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $abilities->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); + + $vis = ['level', 'schools']; + if ($abilities->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8')) + $vis[] = 'reagents'; + + if ($abilities->hasSetFields('reqclass')) + $vis[] = 'classes'; // i'd love to set 'singleclass', but do i want to walk through all abilities to see if each mask contains at most 1 class? + + $lvData = array( + 'data' => $data, + 'id' => 'abilities', + 'name' => '$LANG.tab_abilities', + 'visibleCols' => $vis + ); + + if ($abilities->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_abilitiesfound', $abilities->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=7&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=7&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $abilities->getMatches(), [], [], 'Ability']; + + foreach ($abilities->iterate() as $id => $__) + { + $result[$id] = $abilities->getField('name', true); + $osInfo[2][$id] = $abilities->getField('iconString'); + $osInfo[3][$id] = $abilities->ranks[$id]; + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchTalent() : ?array // 8 Talents (Player + Pet) $moduleMask & 0x0000100 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', [-7, -2]], + $this->createLookup() + )); + $talents = new SpellList($cnd, ['calcTotal' => true]); + + $data = $talents->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $talents->getJSGlobals()); + + $vis = ['level', 'singleclass', 'schools']; + if ($talents->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8')) + $vis[] = 'reagents'; + + $lvData = array( + 'data' => $data, + 'id' => 'talents', + 'name' => '$LANG.tab_talents', + 'visibleCols' => $vis + ); + + if ($talents->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_talentsfound', $talents->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-2&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-2&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $talents->getMatches(), [], [], 'Talent']; + + foreach ($talents->iterate() as $id => $__) + { + $result[$id] = $talents->getField('name', true); + $osInfo[2][$id] = $talents->getField('iconString'); + $osInfo[3][$id] = $talents->ranks[$id]; + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchGlyph() : ?array // 9 Glyphs $moduleMask & 0x0000200 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -13], + $this->createLookup() + )); + $glyphs = new SpellList($cnd, ['calcTotal' => true]); + + $data = $glyphs->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $glyphs->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'id' => 'glyphs', + 'name' => '$LANG.tab_glyphs', + 'visibleCols' => ['singleclass', 'glyphtype'] + ); + + if ($glyphs->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_glyphsfound', $glyphs->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-13&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-13&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $glyphs->getMatches(), [], [], 'Glyph']; + + foreach ($glyphs->iterate() as $id => $__) + { + $result[$id] = $glyphs->getField('name', true); + $osInfo[2][$id] = $glyphs->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchProficiency() : ?array // 10 Proficiencies $moduleMask & 0x0000400 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -11], + $this->createLookup() + )); + $prof = new SpellList($cnd, ['calcTotal' => true]); + + $data = $prof->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $prof->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'id' => 'proficiencies', + 'name' => '$LANG.tab_proficiencies', + 'visibleCols' => ['classes'] + ); + + if ($prof->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $prof->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-11&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-11&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $prof->getMatches(), [], [], 'Proficiency']; + + foreach ($prof->iterate() as $id => $__) + { + $result[$id] = $prof->getField('name', true); + $osInfo[2][$id] = $prof->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchProfession() : ?array // 11 Professions (Primary + Secondary) $moduleMask & 0x0000800 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', [9, 11]], + $this->createLookup() + )); + $prof = new SpellList($cnd, ['calcTotal' => true]); + + $data = $prof->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $prof->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); + + $lvData = array( + 'data' => $data, + 'id' => 'professions', + 'name' => '$LANG.tab_professions', + 'visibleCols' => ['source', 'reagents'] + ); + + if ($prof->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_professionfound', $prof->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=11&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=11&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $prof->getMatches(), [], [], 'Profession']; + + foreach ($prof->iterate() as $id => $__) + { + $result[$id] = $prof->getField('name', true); + $osInfo[2][$id] = $prof->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchCompanion() : ?array // 12 Companions $moduleMask & 0x0001000 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -6], + $this->createLookup() + )); + $vPets = new SpellList($cnd, ['calcTotal' => true]); + + $data = $vPets->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $vPets->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'id' => 'companions', + 'name' => '$LANG.tab_companions', + 'visibleCols' => ['reagents'] + ); + + if ($vPets->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_companionsfound', $vPets->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-6&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-6&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $vPets->getMatches(), [], [], 'Companion']; + + foreach ($vPets->iterate() as $id => $__) + { + $result[$id] = $vPets->getField('name', true); + $osInfo[2][$id] = $vPets->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchMount() : ?array // 13 Mounts $moduleMask & 0x0002000 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -5], + $this->createLookup() + )); + $mounts = new SpellList($cnd, ['calcTotal' => true]); + + $data = $mounts->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $mounts->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'id' => 'mounts', + 'name' => '$LANG.tab_mounts', + ); + + if ($mounts->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_mountsfound', $mounts->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-5&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-5&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $mounts->getMatches(), [], [], 'Mount']; + + foreach ($mounts->iterate() as $id => $__) + { + $result[$id] = $mounts->getField('name', true); + $osInfo[2][$id] = $mounts->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchCreature() : ?array // 14 NPCs $moduleMask & 0x0004000 + { + $cnd = array_merge($this->cndBase, array( + [['flagsExtra', 0x80], 0], // exclude trigger creatures + [['cuFlags', NPC_CU_DIFFICULTY_DUMMY, '&'], 0], // exclude difficulty entries + $this->createLookup() + )); + $npcs = new CreatureList($cnd, ['calcTotal' => true]); + + $data = $npcs->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = array( + 'data' => $data, + 'id' => 'npcs', + 'name' => '$LANG.tab_npcs', + ); + + if ($npcs->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_npcsfound', $npcs->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?npcs&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?npcs&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, CreatureList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::NPC, $npcs->getMatches(), [], [], 'NPC']; + + foreach ($npcs->iterate() as $id => $__) + { + $result[$id] = $npcs->getField('name', true); + if ($npcs->isBoss()) + $osInfo[2][$id] = 1; + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchQuest() : ?array // 15 Quests $moduleMask & 0x0008000 + { + $cnd = array_merge($this->cndBase, array( + [['flags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0], + $this->createLookup() + )); + $quests = new QuestList($cnd, ['calcTotal' => true]); + + $data = $quests->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $quests->getJSGlobals()); + + $lvData = ['data' => $data]; + + if ($quests->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_questsfound', $quests->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?quests&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?quests&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, QuestList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::QUEST, $quests->getMatches(), [], [], 'Quest']; + + foreach ($quests->iterate() as $id => $__) + { + $result[$id] = $quests->getField('name', true); + $osInfo[2][$id] = $data[$id]['side']; // why recalculate if already set + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchAchievement() : ?array // 16 Achievements $moduleMask & 0x0010000 + { + $cnd = array_merge($this->cndBase, array( + [['flags', ACHIEVEMENT_FLAG_COUNTER, '&'], 0], // not a statistic + $this->createLookup() + )); + $acvs = new AchievementList($cnd, ['calcTotal' => true]); + + $data = $acvs->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $acvs->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'visibleCols' => ['category'] + ); + + if ($acvs->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_achievementsfound', $acvs->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?achievements&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?achievements&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, AchievementList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ACHIEVEMENT, $acvs->getMatches(), [], [], 'Achievement']; + + foreach ($acvs->iterate() as $id => $__) + { + $result[$id] = $acvs->getField('name', true); + $osInfo[2][$id] = $acvs->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchStatistic() : ?array // 17 Statistics $moduleMask & 0x0020000 + { + $cnd = array_merge($this->cndBase, array( + ['flags', ACHIEVEMENT_FLAG_COUNTER, '&'], // is a statistic + $this->createLookup() + )); + $stats = new AchievementList($cnd, ['calcTotal' => true]); + + $data = $stats->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $stats->getJSGlobals(GLOBALINFO_SELF)); + + $lvData = array( + 'data' => $data, + 'visibleCols' => ['category'], + 'hiddenCols' => ['side', 'points', 'rewards'], + 'name' => '$LANG.tab_statistics', + 'id' => 'statistics' + ); + + if ($stats->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_statisticsfound', $stats->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?achievements=1&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?achievements=1&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, AchievementList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ACHIEVEMENT, $stats->getMatches(), [], [], 'Statistic']; + + foreach ($stats->iterate() as $id => $__) + { + $result[$id] = $stats->getField('name', true); + $osInfo[2][$id] = $stats->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchZone() : ?array // 18 Zones $moduleMask & 0x0040000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $zones = new ZoneList($cnd, ['calcTotal' => true]); + + $data = $zones->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $zones->getJSGlobals()); + + $lvData = ['data' => $data]; + + if ($zones->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_zonesfound', $zones->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?achievements&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?achievements&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, ZoneList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ZONE, $zones->getMatches(), [], [], 'Zone']; + + foreach ($zones->iterate() as $id => $__) + $result[$id] = $zones->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchObject() : ?array // 19 Objects $moduleMask & 0x0080000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $objects = new GameObjectList($cnd, ['calcTotal' => true]); + + $data = $objects->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $objects->getJSGlobals()); + + $lvData = ['data' => $data]; + + if ($objects->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_objectsfound', $objects->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?objects&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?objects&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, GameObjectList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::OBJECT, $objects->getMatches(), [], [], 'Object']; + + foreach ($objects->iterate() as $id => $__) + $result[$id] = $objects->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchFaction() : ?array // 20 Factions $moduleMask & 0x0100000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $factions = new FactionList($cnd, ['calcTotal' => true]); + + $data = $factions->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($factions->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_factionsfound', $factions->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, FactionList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::FACTION, $factions->getMatches(), [], [], 'Faction']; + + foreach ($factions->iterate() as $id => $__) + $result[$id] = $factions->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchSkill() : ?array // 21 Skills $moduleMask & 0x0200000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $skills = new SkillList($cnd, ['calcTotal' => true]); + + $data = $skills->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = ['data' => $data]; + + if ($skills->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_skillsfound', $skills->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, SkillList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SKILL, $skills->getMatches(), [], [], 'Skill']; + + foreach ($skills->iterate() as $id => $__) + { + $result[$id] = $skills->getField('name', true); + $osInfo[2][$id] = $skills->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchPet() : ?array // 22 Pets $moduleMask & 0x0400000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup()]); + $pets = new PetList($cnd, ['calcTotal' => true]); + + $data = $pets->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + $lvData = array( + 'data' => $data, + 'computeDataFunc' => '$_' + ); + + if ($pets->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_petsfound', $pets->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, PetList::$brickFile, 'petFoodCol']; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::PET, $pets->getMatches(), [], [], 'Pet']; + + foreach ($pets->iterate() as $id => $__) + { + $result[$id] = $pets->getField('name', true); + $osInfo[2][$id] = $pets->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchCreatureAbility() : ?array // 23 NPCAbilities $moduleMask & 0x0800000 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -8], + $this->createLookup() + )); + $npcAbilities = new SpellList($cnd, ['calcTotal' => true]); + + $data = $npcAbilities->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $npcAbilities->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'id' => 'npc-abilities', + 'name' => '$LANG.tab_npcabilities', + 'visibleCols' => ['level'], + 'hiddenCols' => ['skill'] + ); + + if ($npcAbilities->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $npcAbilities->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-8&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-8&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $npcAbilities->getMatches(), [], [], 'Spell']; + + foreach ($npcAbilities->iterate() as $id => $__) + { + $result[$id] = $npcAbilities->getField('name', true); + $osInfo[2][$id] = $npcAbilities->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchSpell() : ?array // 24 Spells (Misc + GM + triggered abilities) $moduleMask & 0x1000000 + { + $cnd = array_merge($this->cndBase, array( + ['s.typeCat', -8, '!'], + [ + 'OR', + ['s.typeCat', [0, -9]], + ['s.cuFlags', SPELL_CU_TRIGGERED, '&'], + ['s.attributes0', 0x80, '&'] + ], + $this->createLookup() + )); + $misc = new SpellList($cnd, ['calcTotal' => true]); + + $data = $misc->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $misc->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); + + $lvData = array( + 'data' => $data, + 'name' => '$LANG.tab_uncategorizedspells', + 'visibleCols' => ['level'], + 'hiddenCols' => ['skill'] + ); + + if ($misc->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $misc->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + if (isset($lvData['note'])) + $lvData['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=0&filter=na='.urlencode($this->query).'\')'; + else + $lvData['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=0&filter=na='.urlencode($this->query).'\')'; + + return [$lvData, SpellList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SPELL, $misc->getMatches(), [], [], 'Spell']; + + foreach ($misc->iterate() as $id => $__) + { + $result[$id] = $misc->getField('name', true); + $osInfo[2][$id] = $misc->getField('iconString'); + } + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchEmote() : ?array // 25 Emotes $moduleMask & 0x2000000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup(['cmd', 'meToExt_loc'.Lang::getLocale()->value, 'meToNone_loc'.Lang::getLocale()->value, 'extToMe_loc'.Lang::getLocale()->value, 'extToExt_loc'.Lang::getLocale()->value, 'extToNone_loc'.Lang::getLocale()->value])]); + $emote = new EmoteList($cnd, ['calcTotal' => true]); + + $data = $emote->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $emote->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'name' => Util::ucFirst(Lang::game('emotes')) + ); + + if ($emote->getMatches() > $this->maxResults) + { + // $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_emotesfound', $emote->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, EmoteList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::EMOTE, $emote->getMatches(), [], [], 'Emote']; + + foreach ($emote->iterate() as $id => $__) + $result[$id] = $emote->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchEnchantment() : ?array // 26 Enchantments $moduleMask & 0x4000000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup(['name_loc'.Lang::getLocale()->value])]); + $enchantment = new EnchantmentList($cnd, ['calcTotal' => true]); + + $data = $enchantment->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $enchantment->getJSGlobals()); + + $lvData = array( + 'data' => $data, + 'name' => Util::ucFirst(Lang::game('enchantments')) + ); + + if (array_filter(array_column($data, 'spells'))) + $lvData['visibleCols'] = ['trigger']; + + if (!$enchantment->hasSetFields('skillLine')) + $lvData['hiddenCols'] = ['skill']; + + if ($enchantment->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_enchantmentsfound', $enchantment->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, EnchantmentList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::ENCHANTMENT, $enchantment->getMatches(), [], [], 'Enchantment']; + + foreach ($enchantment->iterate() as $id => $__) + $result[$id] = $enchantment->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } + + private function _searchSound() : ?array // 27 Sounds $moduleMask & 0x8000000 + { + $cnd = array_merge($this->cndBase, [$this->createLookup(['name'])]); + $sounds = new SoundList($cnd, ['calcTotal' => true]); + + $data = $sounds->getListviewData(); + if (!$data) + return []; + + if ($this->moduleMask & self::TYPE_REGULAR) + { + Util::mergeJsGlobals($this->jsgStore, $sounds->getJSGlobals()); + + $lvData = array( + 'data' => $data, + ); + + if ($sounds->getMatches() > $this->maxResults) + { + $lvData['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_soundsfound', $sounds->getMatches(), $this->maxResults); + $lvData['_truncated'] = 1; + } + + return [$lvData, SoundList::$brickFile]; + } + + if ($this->moduleMask & self::TYPE_OPEN) + { + $result = []; + $osInfo = [Type::SOUND, $sounds->getMatches(), [], [], 'Sound']; + + foreach ($sounds->iterate() as $id => $__) + $result[$id] = $sounds->getField('name', true); + + return [$result, ...$osInfo]; + } + + return null; + } +} diff --git a/includes/dbtypes/worldevent.class.php b/includes/dbtypes/worldevent.class.php index 0bef49e6..18ba67c6 100644 --- a/includes/dbtypes/worldevent.class.php +++ b/includes/dbtypes/worldevent.class.php @@ -79,41 +79,38 @@ class WorldEventList extends DBTypeList return $row ? new LocString($row) : null; } - public static function updateDates($date = null) + public static function updateDates(?array $date = null, ?int &$start = null, ?int &$end = null, ?int &$rec = null) : bool { if (!$date || empty($date['firstDate']) || empty($date['length'])) + return false; + + $start = $date['firstDate']; + $end = $date['firstDate'] + $date['length']; + $rec = $date['rec'] ?: -1; // interval + + if ($rec < 0 || $date['lastDate'] < time()) + return true; + + $nIntervals = ceil((time() - $start) / $rec); + + $start += $nIntervals * $rec; + $end += $nIntervals * $rec; + + return true; + } + + public static function updateListview(Listview &$listview) : void + { + foreach ($listview->iterate() as &$row) { - return array( - 'start' => 0, - 'end' => 0, - 'rec' => 0 - ); + WorldEventList::updateDates($row['_date'] ?? null, $start, $end, $rec); + + $row['startDate'] = $start ? date(Util::$dateFormatInternal, $start) : null; + $row['endDate'] = $end ? date(Util::$dateFormatInternal, $end) : null; + $row['rec'] = $rec; + + unset($row['_date']); } - - // Convert everything to seconds - $firstDate = intVal($date['firstDate']); - $lastDate = !empty($date['lastDate']) ? intVal($date['lastDate']) : 5000000000; // in the far far FAR future..; - $interval = !empty($date['rec']) ? intVal($date['rec']) : -1; - $length = intVal($date['length']); - - $curStart = $firstDate; - $curEnd = $firstDate + $length; - $nextStart = $curStart + $interval; - $nextEnd = $curEnd + $interval; - - while ($interval > 0 && $nextEnd <= $lastDate && $curEnd < time()) - { - $curStart = $nextStart; - $curEnd = $nextEnd; - $nextStart = $curStart + $interval; - $nextEnd = $curEnd + $interval; - } - - return array( - 'start' => $curStart, - 'end' => $curEnd, - 'rec' => $interval - ); } public function getListviewData(bool $forNow = false) : array @@ -139,11 +136,8 @@ class WorldEventList extends DBTypeList { foreach ($data as &$d) { - $u = self::updateDates($d['_date']); + self::updateDates($d['_date'], $d['startDate'], $d['endDate'], $d['rec']); unset($d['_date']); - $d['startDate'] = $u['start']; - $d['endDate'] = $u['end']; - $d['rec'] = $u['rec']; } } diff --git a/includes/defines.php b/includes/defines.php index 86f57e90..194f70e5 100644 --- a/includes/defines.php +++ b/includes/defines.php @@ -55,12 +55,6 @@ define ('SC_FLAG_APPEND_LOCALE', 0x04); define ('SC_FLAG_LOCALIZED', 0x08); -define('SEARCH_TYPE_REGULAR', 0x10000000); -define('SEARCH_TYPE_OPEN', 0x20000000); -define('SEARCH_TYPE_JSON', 0x40000000); -define('SEARCH_MASK_OPEN', 0x007DC1FF); // open search -define('SEARCH_MASK_ALL', 0x0FFFFFFF); // normal search - // Databases define('DB_AOWOW', 0); define('DB_WORLD', 1); diff --git a/pages/event.php b/pages/event.php index 93af1862..2af3aefb 100644 --- a/pages/event.php +++ b/pages/event.php @@ -304,13 +304,13 @@ class EventPage extends GenericPage protected function postCache() { // update dates to now() - $updated = WorldEventList::updateDates($this->dates); + WorldEventList::updateDates($this->dates, $start, $end, $rec); if ($this->mode == CACHE_TYPE_TOOLTIP) { return array( - date(Lang::main('dateFmtLong'), $updated['start']), - date(Lang::main('dateFmtLong'), $updated['end']) + date(Lang::main('dateFmtLong'), $start), + date(Lang::main('dateFmtLong'), $end) ); } else @@ -323,19 +323,19 @@ class EventPage extends GenericPage /********************/ // start - if ($updated['start']) - array_push($this->infobox, Lang::event('start').Lang::main('colon').date(Lang::main('dateFmtLong'), $updated['start'])); + if ($start) + array_push($this->infobox, Lang::event('start').Lang::main('colon').date(Lang::main('dateFmtLong'), $start)); // end - if ($updated['end']) - array_push($this->infobox, Lang::event('end').Lang::main('colon').date(Lang::main('dateFmtLong'), $updated['end'])); + if ($end) + array_push($this->infobox, Lang::event('end').Lang::main('colon').date(Lang::main('dateFmtLong'), $end)); // occurence - if ($updated['rec'] > 0) - array_push($this->infobox, Lang::event('interval').Lang::main('colon').Util::formatTime($updated['rec'] * 1000)); + if ($rec > 0) + array_push($this->infobox, Lang::event('interval').Lang::main('colon').Util::formatTime($rec * 1000)); // in progress - if ($updated['start'] < time() && $updated['end'] > time()) + if ($start < time() && $end > time()) array_push($this->infobox, '[span class=q2]'.Lang::event('inProgress').'[/span]'); $this->infobox = '[ul][li]'.implode('[/li][li]', $this->infobox).'[/li][/ul]'; @@ -351,11 +351,11 @@ class EventPage extends GenericPage foreach ($view[1]['data'] as &$data) { - $updated = WorldEventList::updateDates($data['_date']); + WorldEventList::updateDates($data['_date'], $start, $end, $rec); unset($data['_date']); - $data['startDate'] = $updated['start'] ? date(Util::$dateFormatInternal, $updated['start']) : false; - $data['endDate'] = $updated['end'] ? date(Util::$dateFormatInternal, $updated['end']) : false; - $data['rec'] = $updated['rec']; + $data['startDate'] = $start ? date(Util::$dateFormatInternal, $start) : false; + $data['endDate'] = $end ? date(Util::$dateFormatInternal, $end) : false; + $data['rec'] = $rec; } } } diff --git a/pages/events.php b/pages/events.php index 4807ce20..4ed714c1 100644 --- a/pages/events.php +++ b/pages/events.php @@ -96,11 +96,11 @@ class EventsPage extends GenericPage continue; } - $updated = WorldEventList::updateDates($data['_date']); + WorldEventList::updateDates($data['_date'], $start, $end, $rec); unset($data['_date']); - $data['startDate'] = $updated['start'] ? date(Util::$dateFormatInternal, $updated['start']) : false; - $data['endDate'] = $updated['end'] ? date(Util::$dateFormatInternal, $updated['end']) : false; - $data['rec'] = $updated['rec']; + $data['startDate'] = $start ? date(Util::$dateFormatInternal, $start) : false; + $data['endDate'] = $end ? date(Util::$dateFormatInternal, $end) : false; + $data['rec'] = $rec; } } } diff --git a/pages/search.php b/pages/search.php deleted file mode 100644 index fabf70a1..00000000 --- a/pages/search.php +++ /dev/null @@ -1,1393 +0,0 @@ - search by compare or profiler - else if &opensearch - => suggestions when typing into searchboxes - array:[ - str, // search - str[10], // found - [], // unused - [], // unused - [], // unused - [], // unused - [], // unused - str[10][4] // type, typeId, param1 (4:quality, 3,6,9,10,17:icon, 5,11:faction), param2 (3:quality, 6:rank) - ] - else - => listviews -*/ - - -// tabId 0: Database g_initHeader() -class SearchPage extends GenericPage -{ - protected $tpl = 'search'; - protected $tabId = 0; - protected $mode = CACHE_TYPE_SEARCH; - protected $scripts = [[SC_JS_FILE, 'js/swfobject.js']]; - protected $lvTabs = []; // [file, data, extraInclude, osInfo] // osInfo:[type, appendix, nMatches, param1, param2] - protected $forceTabs = true; - protected $search = ''; // output - protected $invalid = []; - - protected $_get = array( - 'wt' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkIntArray'], - 'wtv' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkIntArray'], - 'slots' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkIntArray'], - 'type' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkInt'], - 'json' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkEmptySet'], - 'opensearch' => ['filter' => FILTER_CALLBACK, 'options' => 'Aowow\GenericPage::checkEmptySet'] - ); - - private $maxResults = 500; - private $searchMask = 0x0; - private $query = ''; // lookup - private $included = []; - private $excluded = []; - private $statWeights = []; - private $searches = array( - '_searchCharClass', '_searchCharRace', '_searchTitle', '_searchWorldEvent', '_searchCurrency', - '_searchItemset', '_searchItem', '_searchAbility', '_searchTalent', '_searchGlyph', - '_searchProficiency', '_searchProfession', '_searchCompanion', '_searchMount', '_searchCreature', - '_searchQuest', '_searchAchievement', '_searchStatistic', '_searchZone', '_searchObject', - '_searchFaction', '_searchSkill', '_searchPet', '_searchCreatureAbility', '_searchSpell', - '_searchEmote', '_searchEnchantment', '_searchSound' - ); - - public function __construct($pageCall, $pageParam) - { - parent::__construct($pageCall, $pageParam); // just to set g_user and g_locale - - $this->search = - $this->query = trim(urlDecode($pageParam)); - - // sanitize stat weights - if ($this->_get['wt'] && $this->_get['wtv']) - { - $wt = $this->_get['wt']; - $wtv = $this->_get['wtv']; - $nwt = count($wt); - $nwtv = count($wtv); - - if ($nwt > $nwtv) - array_splice($wt, $nwtv); - else if ($nwtv > $nwt) - array_splice($wtv, $nwt); - - if ($wt && $wtv) - $this->statWeights = [$wt, $wtv]; - } - - if ($limit = Cfg::get('SQL_LIMIT_SEARCH')) - $this->maxResults = $limit; - - // select search mode - if ($this->_get['json']) - { - if ($_ = intVal($this->search)) // allow for search by Id - $this->query = $_; - - if ($this->_get['slots']) - $this->searchMask |= SEARCH_TYPE_JSON | 0x40; - else if ($this->_get['type'] == Type::ITEMSET) - $this->searchMask |= SEARCH_TYPE_JSON | 0x60; - else if ($this->_get['type'] == Type::ITEM) - $this->searchMask |= SEARCH_TYPE_JSON | 0x40; - } - else if ($this->_get['opensearch']) - { - $this->maxResults = Cfg::get('SQL_LIMIT_QUICKSEARCH'); - $this->searchMask |= SEARCH_TYPE_OPEN | SEARCH_MASK_OPEN; - } - else - $this->searchMask |= SEARCH_TYPE_REGULAR | SEARCH_MASK_ALL; - - // handle maintenance status for js-cases - if (Cfg::get('MAINTENANCE') && !User::isInGroup(U_GROUP_EMPLOYEE) && !($this->searchMask & SEARCH_TYPE_REGULAR)) - $this->notFound(); - - // fill include, exclude and ignore - $this->tokenizeQuery(); - - // invalid conditions: not enough characters to search OR no types to search - if ((Cfg::get('MAINTENANCE') && !User::isInGroup(U_GROUP_EMPLOYEE)) || - (!$this->included && ($this->searchMask & (SEARCH_TYPE_OPEN | SEARCH_TYPE_REGULAR))) || - (($this->searchMask & SEARCH_TYPE_JSON) && !$this->included && !$this->statWeights)) - { - $this->mode = CACHE_TYPE_NONE; - $this->notFound(); - } - } - - private function tokenizeQuery() - { - if (!$this->query) - return; - - foreach (explode(' ', $this->query) as $raw) - { - $clean = str_replace(['\\', '%'], '', $raw); - - if (!$clean) // multiple spaces - continue; - else if ($clean[0] == '-') - { - if (mb_strlen($clean) < 4 && !Lang::getLocale()->isLogographic()) - $this->invalid[] = mb_substr($raw, 1); - else - $this->excluded[] = mb_substr(str_replace('_', '\\_', $clean), 1); - } - else if ($clean !== '') - { - if (mb_strlen($clean) < 3 && !Lang::getLocale()->isLogographic()) - $this->invalid[] = $raw; - else - $this->included[] = str_replace('_', '\\_', $clean); - } - } - } - - protected function generateCacheKey($withStaff = true) - { - $staff = intVal($withStaff && User::isInGroup(U_GROUP_EMPLOYEE)); - - $key = [$this->mode, $this->searchMask, md5($this->query), $staff, Lang::getLocale()->value]; - - return implode('_', $key); - } - - protected function postCache() - { - if (!empty($this->lvTabs[3])) // has world events - { - // update WorldEvents to date() - foreach ($this->lvTabs[3][1]['data'] as &$d) - { - $updated = WorldEventList::updateDates($d['_date']); - unset($d['_date']); - $d['startDate'] = $updated['start'] ? date(Util::$dateFormatInternal, $updated['start']) : false; - $d['endDate'] = $updated['end'] ? date(Util::$dateFormatInternal, $updated['end']) : false; - $d['rec'] = $updated['rec']; - } - } - - if ($this->searchMask & SEARCH_TYPE_REGULAR) - { - $foundTotal = 0; - foreach ($this->lvTabs as [, $tabData]) - $foundTotal += count($tabData['data']); - - if ($foundTotal == 1) // only one match -> redirect to find - { - $tab = array_pop($this->lvTabs); - $type = Type::getFileString($tab[3][0]); - $typeId = array_pop($tab[1]['data'])['id']; - - header('Location: ?'.$type.'='.$typeId, true, 302); - exit(); - } - } - } - - protected function generateTitle() - { - array_unshift($this->title, $this->search, Lang::main('search')); - } - - protected function generatePath() { } - - protected function generateContent() // just wrap it, so GenericPage can call and cache it - { - if ($this->mode == CACHE_TYPE_NONE) // search is invalid - return; - - $this->addScript([SC_JS_FILE, '?data=zones']); - - $this->performSearch(); - } - - public function notFound(string $title = '', string $msg = '') : never - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - { - // empty queries go home - if (!$this->query) - { - header('Location: .', true, 302); - die(); - } - - parent::display(); // errors are handled in the search-template itself - } - else if ($this->searchMask & SEARCH_TYPE_OPEN) - { - header(MIME_TYPE_OPENSEARCH); - exit(Util::toJSON([$this->search, []])); - } - else if ($this->searchMask & SEARCH_TYPE_JSON) - { - header(MIME_TYPE_JSON); - exit(Util::toJSON([$this->search, [], []])); - } - - exit; - } - - public function display(string $override = '') : never - { - if ($override || ($this->searchMask & SEARCH_TYPE_REGULAR)) - parent::display($override); - else if ($this->searchMask & SEARCH_TYPE_OPEN) - $this->displayExtra([$this, 'generateOpenSearch'], MIME_TYPE_OPENSEARCH); - else if ($this->searchMask & SEARCH_TYPE_JSON) - $this->displayExtra([$this, 'generateJsonSearch']); - - exit; - } - - // !note! dear reader, if you ever try to generate a string, that is to be evaled by JS, NEVER EVER terminate with a \n ..... $totalHoursWasted +=2; - protected function generateJsonSearch() - { - $outItems = []; - $outSets = []; - - $this->performSearch(); - - // items - if (!empty($this->lvTabs[6][1]['data'])) - $outItems = array_values($this->lvTabs[6][1]['data']); - - // item sets - if (!empty($this->lvTabs[5][1]['data'])) - { - foreach ($this->lvTabs[5][1]['data'] as $k => $v) - { - unset($v['quality']); - if (!$v['heroic']) - unset($v['heroic']); - - $outSets[] = $v; - } - } - - return Util::toJSON([$this->search, $outItems, $outSets]); - } - - protected function generateOpenSearch() - { - // this one is funny: we want 10 results, ideally equally distributed over each type - $foundTotal = 0; - $limit = $this->maxResults; - $result = array( //idx1: names, idx3: resultUrl; idx7: extraInfo - $this->search, - [], [], [], [], [], [], [] - ); - - $this->performSearch(); - - foreach ($this->lvTabs as [, , , $osInfo]) - $foundTotal += $osInfo[2]; - - if (!$foundTotal) - return Util::toJSON([$this->search, []]); - - foreach ($this->lvTabs as [, $tabData, , $osInfo]) - { - $max = max(1, intVal($limit * $osInfo[2] / $foundTotal)); - $limit -= $max; - - for ($i = 0; $i < $max; $i++) - { - $data = array_shift($tabData['data']); - if (!$data) - break; - - $hasQ = is_numeric($data['name'][0]) || $data['name'][0] == '@'; - $result[1][] = ($hasQ ? mb_substr($data['name'], 1) : $data['name']).$osInfo[1]; - $result[3][] = Cfg::get('HOST_URL').'/?'.Type::getFileString($osInfo[0]).'='.$data['id']; - - $extra = [$osInfo[0], $data['id']]; // type, typeId - - if (isset($osInfo[3][$data['id']])) - $extra[] = $osInfo[3][$data['id']]; // param1 - - if (isset($osInfo[4][$data['id']])) - $extra[] = $osInfo[4][$data['id']]; // param2 - - $result[7][] = $extra; - } - - if ($limit <= 0) - break; - } - - return Util::toJSON($result); - } - - private function createLookup(array $fields = []) - { - // default to name-field - if (!$fields) - $fields[] = 'name_loc'.Lang::getLocale()->value; - - $qry = []; - foreach ($fields as $f) - { - $sub = []; - foreach ($this->included as $i) - $sub[] = [$f, '%'.$i.'%']; - - foreach ($this->excluded as $x) - $sub[] = [$f, '%'.$x.'%', '!']; - - // single cnd? - if (count($sub) > 1) - array_unshift($sub, 'AND'); - else - $sub = $sub[0]; - - $qry[] = $sub; - } - - // single cnd? - if (count($qry) > 1) - array_unshift($qry, 'OR'); - else - $qry = $qry[0]; - - return $qry; - } - - private function performSearch() - { - $cndBase = ['AND', $this->maxResults]; - - // Exclude internal wow stuff - if (!User::isInGroup(U_GROUP_EMPLOYEE)) - $cndBase[] = [['cuFlags', CUSTOM_EXCLUDE_FOR_LISTVIEW, '&'], 0]; - - $shared = []; - foreach ($this->searches as $idx => $ref) - if ($this->searchMask & (1 << $idx)) - if ($_ = $this->$ref($cndBase, $shared)) - $this->lvTabs[$idx] = $_; - } - - private function _searchCharClass($cndBase) // 0 Classes: $searchMask & 0x00000001 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $classes = new CharClassList($cnd, ['calcTotal' => true]); - - if ($data = $classes->getListviewData()) - { - $result['data'] = array_values($data); - $osInfo = [Type::CHR_CLASS, ' (Class)', $classes->getMatches(), []]; - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($classes->iterate() as $id => $__) - $osInfo[3][$id] = 'class_'.strToLower($classes->getField('fileString')); - - if ($classes->getMatches() > $this->maxResults) - { - // $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $classes->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [CharClassList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchCharRace($cndBase) // 1 Races: $searchMask & 0x00000002 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $races = new CharRaceList($cnd, ['calcTotal' => true]); - - if ($data = $races->getListviewData()) - { - $result['data'] = array_values($data); - $osInfo = [Type::CHR_RACE, ' (Race)', $races->getMatches(), []]; - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($races->iterate() as $id => $__) - $osInfo[3][$id] = 'race_'.strToLower($races->getField('fileString')).'_male'; - - if ($races->getMatches() > $this->maxResults) - { - // $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $races->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [CharRaceList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchTitle($cndBase) // 2 Titles: $searchMask & 0x00000004 - { - $cnd = array_merge($cndBase, [$this->createLookup(['male_loc'.Lang::getLocale()->value, 'female_loc'.Lang::getLocale()->value])]); - $titles = new TitleList($cnd, ['calcTotal' => true]); - - if ($data = $titles->getListviewData()) - { - $result['data'] = array_values($data); - $osInfo = [Type::TITLE, ' (Title)', $titles->getMatches(), []]; - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($titles->iterate() as $id => $__) - $osInfo[3][$id] = $titles->getField('side'); - - if ($titles->getMatches() > $this->maxResults) - { - // $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $titles->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [TitleList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchWorldEvent($cndBase) // 3 World Events: $searchMask & 0x00000008 - { - $cnd = array_merge($cndBase, array( - array( - 'OR', - $this->createLookup(['h.name_loc'.Lang::getLocale()->value]), - ['AND', $this->createLookup(['e.description']), ['e.holidayId', 0]] - ) - )); - $wEvents = new WorldEventList($cnd, ['calcTotal' => true]); - - if ($data = $wEvents->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($wEvents->getJSGlobals()); - - $result['data'] = array_values($data); - $osInfo = [Type::WORLDEVENT, ' (World Event)', $wEvents->getMatches()]; - - // as allways: dates are updated in postCache-step - - if ($wEvents->getMatches() > $this->maxResults) - { - // $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_', $wEvents->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [WorldEventList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchCurrency($cndBase) // 4 Currencies $searchMask & 0x0000010 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $money = new CurrencyList($cnd, ['calcTotal' => true]); - - if ($data = $money->getListviewData()) - { - $result['data'] = array_values($data); - $osInfo = [Type::CURRENCY, ' (Currency)', $money->getMatches()]; - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($money->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($money->getField('iconString')); - - if ($money->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_currenciesfound', $money->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [CurrencyList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchItemset($cndBase, &$shared) // 5 Itemsets $searchMask & 0x0000020 - { - $cnd = array_merge($cndBase, [is_int($this->query) ? ['id', $this->query] : $this->createLookup()]); - $sets = new ItemsetList($cnd, ['calcTotal' => true]); - - if ($data = $sets->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($sets->getJSGlobals(GLOBALINFO_SELF)); - - $result['data'] = array_values($data); - $osInfo = [Type::ITEMSET, ' (Item Set)', $sets->getMatches()]; - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($sets->iterate() as $id => $__) - $osInfo[3][$id] = $sets->getField('quality'); - - $shared['pcsToSet'] = $sets->pieceToSet; - - if ($sets->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_itemsetsfound', $sets->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?itemsets&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?itemsets&filter=na='.urlencode($this->search).'\')'; - - return [ItemsetList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchItem($cndBase, &$shared) // 6 Items $searchMask & 0x0000040 - { - $miscData = ['calcTotal' => true]; - $cndAdd = empty($this->query) ? [] : (is_int($this->query) ? ['id', $this->query] : $this->createLookup()); - - if (($this->searchMask & SEARCH_TYPE_JSON) && ($this->searchMask & 0x20) && !empty($shared['pcsToSet'])) - { - $cnd = [['i.id', array_keys($shared['pcsToSet'])], Cfg::get('SQL_LIMIT_NONE')]; - $miscData = ['pcsToSet' => $shared['pcsToSet']]; - } - else if (($this->searchMask & SEARCH_TYPE_JSON) && ($this->searchMask & 0x40)) - { - $cnd = $cndBase; - $cnd[] = ['i.class', [ITEM_CLASS_WEAPON, ITEM_CLASS_GEM, ITEM_CLASS_ARMOR]]; - $cnd[] = $cndAdd; - - if ($_ = array_filter($this->_get['slots'] ?? [])) - $cnd[] = ['slot', $_]; - - if ($this->_get['wt'] && $this->_get['wtv']) - { - $itemFilter = new ItemListFilter($this->_get); - if ($_ = $itemFilter->createConditionsForWeights()) - { - $miscData['extraOpts'] = $itemFilter->extraOpts; - $cnd = array_merge($cnd, [$_]); - } - } - } - else - $cnd = array_merge($cndBase, [$cndAdd]); - - $items = new ItemList($cnd, $miscData); - - if ($data = $items->getListviewData($this->searchMask & SEARCH_TYPE_JSON ? (ITEMINFO_SUBITEMS | ITEMINFO_JSON) : 0)) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($items->getJSGlobals()); - - foreach ($items->iterate() as $itemId => $__) - if (!empty($data[$itemId]['subitems'])) - foreach ($data[$itemId]['subitems'] as &$si) - $si['enchantment'] = implode(', ', $si['enchantment']); - - $osInfo = [Type::ITEM, ' (Item)', $items->getMatches(), [], []]; - $result['data'] = array_values($data); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - { - foreach ($items->iterate() as $id => $__) - { - $osInfo[3][$id] = $items->getField('iconString'); - $osInfo[4][$id] = $items->getField('quality'); - } - } - - if ($items->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_itemsfound', $items->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?items&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?items&filter=na='.urlencode($this->search).'\')'; - - return [ItemList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchAbility($cndBase) // 7 Abilities (Player + Pet) $searchMask & 0x0000080 - { - $cnd = array_merge($cndBase, array( // hmm, inclued classMounts..? - ['s.typeCat', [7, -2, -3, -4]], - [['s.cuFlags', (SPELL_CU_TRIGGERED | SPELL_CU_TALENT), '&'], 0], - [['s.attributes0', 0x80, '&'], 0], - $this->createLookup() - )); - $abilities = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $abilities->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($abilities->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); - - $vis = ['level', 'schools']; - if ($abilities->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8')) - $vis[] = 'reagents'; - - $vis[] = count(ChrClass::fromMask($d['reqclass'] ?? 0)) > 1 ? 'classes' : 'singleclass'; - - $osInfo = [Type::SPELL, ' (Ability)', $abilities->getMatches(), [], []]; - $result = array( - 'data' => array_values($data), - 'id' => 'abilities', - 'name' => '$LANG.tab_abilities', - 'visibleCols' => $vis - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - { - foreach ($abilities->iterate() as $id => $__) - { - $osInfo[3][$id] = strToLower($abilities->getField('iconString')); - $osInfo[4][$id] = $abilities->ranks[$id]; - } - } - - if ($abilities->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_abilitiesfound', $abilities->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=7&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=7&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchTalent($cndBase) // 8 Talents (Player + Pet) $searchMask & 0x0000100 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', [-7, -2]], - $this->createLookup() - )); - $talents = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $talents->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($talents->getJSGlobals()); - - $vis = ['level', 'singleclass', 'schools']; - if ($talents->hasSetFields('reagent1', 'reagent2', 'reagent3', 'reagent4', 'reagent5', 'reagent6', 'reagent7', 'reagent8')) - $vis[] = 'reagents'; - - $osInfo = [Type::SPELL, ' (Talent)', $talents->getMatches(), [], []]; - $result = array( - 'data' => array_values($data), - 'id' => 'talents', - 'name' => '$LANG.tab_talents', - 'visibleCols' => $vis - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - { - foreach ($talents->iterate() as $id => $__) - { - $osInfo[3][$id] = strToLower($talents->getField('iconString')); - $osInfo[4][$id] = $talents->ranks[$talents->id]; - } - } - - if ($talents->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_talentsfound', $talents->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-2&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-2&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchGlyph($cndBase) // 9 Glyphs $searchMask & 0x0000200 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -13], - $this->createLookup() - )); - $glyphs = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $glyphs->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($glyphs->getJSGlobals(GLOBALINFO_SELF)); - - $osInfo = [Type::SPELL, ' (Glyph)', $glyphs->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'id' => 'glyphs', - 'name' => '$LANG.tab_glyphs', - 'visibleCols' => ['singleclass', 'glyphtype'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($glyphs->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($glyphs->getField('iconString')); - - if ($glyphs->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_glyphsfound', $glyphs->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-13&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-13&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchProficiency($cndBase) // 10 Proficiencies $searchMask & 0x0000400 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -11], - $this->createLookup() - )); - $prof = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $prof->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($prof->getJSGlobals(GLOBALINFO_SELF)); - - $osInfo = [Type::SPELL, ' (Proficiency)', $prof->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'id' => 'proficiencies', - 'name' => '$LANG.tab_proficiencies', - 'visibleCols' => ['classes'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($prof->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($prof->getField('iconString')); - - if ($prof->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $prof->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-11&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-11&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchProfession($cndBase) // 11 Professions (Primary + Secondary) $searchMask & 0x0000800 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', [9, 11]], - $this->createLookup() - )); - $prof = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $prof->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($prof->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); - - $osInfo = [Type::SPELL, ' (Profession)', $prof->getMatches()]; - $result = array( - 'data' => array_values($data), - 'id' => 'professions', - 'name' => '$LANG.tab_professions', - 'visibleCols' => ['source', 'reagents'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($prof->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($prof->getField('iconString')); - - if ($prof->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_professionfound', $prof->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=11&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=11&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchCompanion($cndBase) // 12 Companions $searchMask & 0x0001000 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -6], - $this->createLookup() - )); - $vPets = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $vPets->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($vPets->getJSGlobals()); - - $osInfo = [Type::SPELL, ' (Companion)', $vPets->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'id' => 'companions', - 'name' => '$LANG.tab_companions', - 'visibleCols' => ['reagents'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($vPets->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($vPets->getField('iconString')); - - if ($vPets->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_companionsfound', $vPets->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-6&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-6&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchMount($cndBase) // 13 Mounts $searchMask & 0x0002000 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -5], - $this->createLookup() - )); - $mounts = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $mounts->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($mounts->getJSGlobals(GLOBALINFO_SELF)); - - $osInfo = [Type::SPELL, ' (Mount)', $mounts->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'id' => 'mounts', - 'name' => '$LANG.tab_mounts', - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($mounts->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($mounts->getField('iconString')); - - if ($mounts->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_mountsfound', $mounts->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-5&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-5&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchCreature($cndBase) // 14 NPCs $searchMask & 0x0004000 - { - $cnd = array_merge($cndBase, array( - [['flagsExtra', 0x80], 0], // exclude trigger creatures - [['cuFlags', NPC_CU_DIFFICULTY_DUMMY, '&'], 0], // exclude difficulty entries - $this->createLookup() - )); - $npcs = new CreatureList($cnd, ['calcTotal' => true]); - - if ($data = $npcs->getListviewData()) - { - $osInfo = [Type::NPC, ' (NPC)', $npcs->getMatches()]; - $result = array( - 'data' => array_values($data), - 'id' => 'npcs', - 'name' => '$LANG.tab_npcs', - ); - - if ($npcs->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_npcsfound', $npcs->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?npcs&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?npcs&filter=na='.urlencode($this->search).'\')'; - - return [CreatureList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchQuest($cndBase) // 15 Quests $searchMask & 0x0008000 - { - $cnd = array_merge($cndBase, array( - [['flags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0], - $this->createLookup() - )); - $quests = new QuestList($cnd, ['calcTotal' => true]); - - if ($data = $quests->getListviewData()) - { - $osInfo = [Type::QUEST, ' (Quest)', $quests->getMatches()]; - $result['data'] = array_values($data); - - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($quests->getJSGlobals()); - - if ($quests->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_questsfound', $quests->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?quests&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?quests&filter=na='.urlencode($this->search).'\')'; - - return [QuestList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchAchievement($cndBase) // 16 Achievements $searchMask & 0x0010000 - { - $cnd = array_merge($cndBase, array( - [['flags', ACHIEVEMENT_FLAG_COUNTER, '&'], 0], // not a statistic - $this->createLookup() - )); - $acvs = new AchievementList($cnd, ['calcTotal' => true]); - - if ($data = $acvs->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($acvs->getJSGlobals()); - - $osInfo = [Type::ACHIEVEMENT, ' (Achievement)', $acvs->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'visibleCols' => ['category'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($acvs->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($acvs->getField('iconString')); - - if ($acvs->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_achievementsfound', $acvs->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?achieveemnts&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?achievements&filter=na='.urlencode($this->search).'\')'; - - return [AchievementList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchStatistic($cndBase) // 17 Statistics $searchMask & 0x0020000 - { - $cnd = array_merge($cndBase, array( - ['flags', ACHIEVEMENT_FLAG_COUNTER, '&'], // is a statistic - $this->createLookup() - )); - $stats = new AchievementList($cnd, ['calcTotal' => true]); - - if ($data = $stats->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($stats->getJSGlobals(GLOBALINFO_SELF)); - - $osInfo = [Type::ACHIEVEMENT, ' (Statistic)', $stats->getMatches()]; - $result = array( - 'data' => array_values($data), - 'visibleCols' => ['category'], - 'hiddenCols' => ['side', 'points', 'rewards'], - 'name' => '$LANG.tab_statistics', - 'id' => 'statistics' - ); - - if ($stats->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_statisticsfound', $stats->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?achievements=1&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?achievements=1&filter=na='.urlencode($this->search).'\')'; - - return [AchievementList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchZone($cndBase) // 18 Zones $searchMask & 0x0040000 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $zones = new ZoneList($cnd, ['calcTotal' => true]); - - if ($data = $zones->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($zones->getJSGlobals()); - - $osInfo = [Type::ZONE, ' (Zone)', $zones->getMatches()]; - $result['data'] = array_values($data); - - if ($zones->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_zonesfound', $zones->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [ZoneList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchObject($cndBase) // 19 Objects $searchMask & 0x0080000 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $objects = new GameObjectList($cnd, ['calcTotal' => true]); - - if ($data = $objects->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($objects->getJSGlobals()); - - $osInfo = [Type::OBJECT, ' (Object)', $objects->getMatches()]; - $result['data'] = array_values($data); - - if ($objects->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_objectsfound', $objects->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?objects&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?objects&filter=na='.urlencode($this->search).'\')'; - - return [GameObjectList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchFaction($cndBase) // 20 Factions $searchMask & 0x0100000 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $factions = new FactionList($cnd, ['calcTotal' => true]); - - if ($data = $factions->getListviewData()) - { - $osInfo = [Type::FACTION, ' (Faction)', $factions->getMatches()]; - $result['data'] = array_values($data); - - if ($factions->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_factionsfound', $factions->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [FactionList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchSkill($cndBase) // 21 Skills $searchMask & 0x0200000 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $skills = new SkillList($cnd, ['calcTotal' => true]); - - if ($data = $skills->getListviewData()) - { - $osInfo = [Type::SKILL, ' (Skill)', $skills->getMatches(), []]; - $result['data'] = array_values($data); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($skills->iterate() as $id => $__) - $osInfo[3][$id] = $skills->getField('iconString'); - - if ($skills->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_skillsfound', $skills->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [SkillList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchPet($cndBase) // 22 Pets $searchMask & 0x0400000 - { - $cnd = array_merge($cndBase, [$this->createLookup()]); - $pets = new PetList($cnd, ['calcTotal' => true]); - - if ($data = $pets->getListviewData()) - { - $osInfo = [Type::PET, ' (Pet)', $pets->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'computeDataFunc' => '$_' - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($pets->iterate() as $id => $__) - $osInfo[3][$id] = $pets->getField('iconString'); - - if ($pets->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_petsfound', $pets->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [PetList::$brickFile, $result, 'petFoodCol', $osInfo]; - } - - return false; - } - - private function _searchCreatureAbility($cndBase) // 23 NPCAbilities $searchMask & 0x0800000 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -8], - $this->createLookup() - )); - $npcAbilities = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $npcAbilities->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($npcAbilities->getJSGlobals(GLOBALINFO_SELF)); - - $osInfo = [Type::SPELL, ' (Spell)', $npcAbilities->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'id' => 'npc-abilities', - 'name' => '$LANG.tab_npcabilities', - 'visibleCols' => ['level'], - 'hiddenCols' => ['skill'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($npcAbilities->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($npcAbilities->getField('iconString')); - - if ($npcAbilities->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $npcAbilities->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=-8&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=-8&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchSpell($cndBase) // 24 Spells (Misc + GM + triggered abilities) $searchMask & 0x1000000 - { - $cnd = array_merge($cndBase, array( - ['s.typeCat', -8, '!'], - [ - 'OR', - ['s.typeCat', [0, -9]], - ['s.cuFlags', SPELL_CU_TRIGGERED, '&'], - ['s.attributes0', 0x80, '&'] - ], - $this->createLookup() - )); - $misc = new SpellList($cnd, ['calcTotal' => true]); - - if ($data = $misc->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($misc->getJSGlobals(GLOBALINFO_SELF | GLOBALINFO_RELATED)); - - $osInfo = [Type::SPELL, ' (Spell)', $misc->getMatches(), []]; - $result = array( - 'data' => array_values($data), - 'name' => '$LANG.tab_uncategorizedspells', - 'visibleCols' => ['level'], - 'hiddenCols' => ['skill'] - ); - - if ($this->searchMask & SEARCH_TYPE_OPEN) - foreach ($misc->iterate() as $id => $__) - $osInfo[3][$id] = strToLower($misc->getField('iconString')); - - if ($misc->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_spellsfound', $misc->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - if (isset($result['note'])) - $result['note'] .= ' + LANG.dash + $WH.sprintf(LANG.lvnote_filterresults, \'?spells=0&filter=na='.urlencode($this->search).'\')'; - else - $result['note'] = '$$WH.sprintf(LANG.lvnote_filterresults, \'?spells=0&filter=na='.urlencode($this->search).'\')'; - - return [SpellList::$brickFile, $result, null, $osInfo]; - } - - return false; - } - - private function _searchEmote($cndBase) // 25 Emotes $searchMask & 0x2000000 - { - $cnd = array_merge($cndBase, [$this->createLookup(['cmd', 'meToExt_loc'.Lang::getLocale()->value, 'meToNone_loc'.Lang::getLocale()->value, 'extToMe_loc'.Lang::getLocale()->value, 'extToExt_loc'.Lang::getLocale()->value, 'extToNone_loc'.Lang::getLocale()->value])]); - $emote = new EmoteList($cnd, ['calcTotal' => true]); - - if ($data = $emote->getListviewData()) - { - $osInfo = [Type::EMOTE, ' (Emote)', $emote->getMatches()]; - $result = array( - 'data' => array_values($data), - 'name' => Util::ucFirst(Lang::game('emotes')) - ); - - return [EmoteList::$brickFile, $result, 'emote', $osInfo]; - } - - return false; - } - - private function _searchEnchantment($cndBase) // 26 Enchantments $searchMask & 0x4000000 - { - $cnd = array_merge($cndBase, [$this->createLookup(['name_loc'.Lang::getLocale()->value])]); - $enchantment = new EnchantmentList($cnd, ['calcTotal' => true]); - - if ($data = $enchantment->getListviewData()) - { - $this->extendGlobalData($enchantment->getJSGlobals()); - - $osInfo = [Type::ENCHANTMENT, ' (Enchantment)', $enchantment->getMatches()]; - $result = array( - 'data' => array_values($data), - 'name' => Util::ucFirst(Lang::game('enchantments')) - ); - - if (array_filter(array_column($result['data'], 'spells'))) - $result['visibleCols'] = ['trigger']; - - if (!$enchantment->hasSetFields('skillLine')) - $result['hiddenCols'] = ['skill']; - - if ($enchantment->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_enchantmentsfound', $enchantment->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [EnchantmentList::$brickFile, $result, 'enchantment', $osInfo]; - } - - return false; - } - - private function _searchSound($cndBase) // 27 Sounds $searchMask & 0x8000000 - { - $cnd = array_merge($cndBase, [$this->createLookup(['name'])]); - $sounds = new SoundList($cnd, ['calcTotal' => true]); - - if ($data = $sounds->getListviewData()) - { - if ($this->searchMask & SEARCH_TYPE_REGULAR) - $this->extendGlobalData($sounds->getJSGlobals()); - - $osInfo = [Type::SOUND, ' (Sound)', $sounds->getMatches()]; - $result['data'] = array_values($data); - - if ($sounds->getMatches() > $this->maxResults) - { - $result['note'] = sprintf(Util::$tryNarrowingString, 'LANG.lvnote_soundsfound', $sounds->getMatches(), $this->maxResults); - $result['_truncated'] = 1; - } - - return [SoundList::$brickFile, $result, null, $osInfo]; - } - - return false; - } -} - -?> diff --git a/static/js/global.js b/static/js/global.js index 9be5888a..9cdceb4b 100644 --- a/static/js/global.js +++ b/static/js/global.js @@ -18804,7 +18804,9 @@ var LiveSearch = new function() { function highlight(match, $1) { // $1 containts % in matches with %s, which we don't want to replace - return ($1 ? match : '' + match + ''); + return ($1 ? '' + match + '' : match); + // aowow - why was the ternary reversed? Also how can it not match .. we explicitly searched for it. + // return ($1 ? match : '' + match + ''); } function display(textbox, search, suggz, dataz) { diff --git a/template/pages/search.tpl.php b/template/pages/search.tpl.php index a5b74b5a..a0845be4 100644 --- a/template/pages/search.tpl.php +++ b/template/pages/search.tpl.php @@ -1,7 +1,10 @@ - +brick('header'); ?> + use \Aowow\Lang; + $this->brick('header'); +?>
@@ -13,12 +16,12 @@ $this->brick('pageTemplate'); ?>
- WowheadWowhead lvTabs): - echo '

'.Lang::main('foundResult').' '.Util::htmlEscape($this->search).''; - if ($this->invalid): - echo ''.sprintf(Lang::main('ignoredTerms'), implode(', ', $this->invalid)).''; +$this->brick('redButtons'); +if (count($this->lvTabs)): + echo '

'.Lang::main('foundResult').' '.$this->search.''; + if ($this->invalidTerms): + echo ''.Lang::main('ignoredTerms', [$this->invalidTerms]).''; endif; echo "

\n"; ?> @@ -27,9 +30,9 @@ if ($this->lvTabs): $this->brick('lvTabs'); else: - echo '

'.Lang::main('noResult').' '.Util::htmlEscape($this->search).''; - if ($this->invalid): - echo ''.sprintf(Lang::main('ignoredTerms'), implode(', ', $this->invalid)).''; + echo '

'.Lang::main('noResult').' '.$this->search.''; + if ($this->invalidTerms): + echo ''.Lang::main('ignoredTerms', [$this->invalidTerms]).''; endif; echo "

\n"; ?>