diff --git a/README.md b/README.md index 3e4aa9b7..34c6fa94 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,8 @@ audio processing may require [lame](https://sourceforge.net/projects/lame/files/ Ensure that the account you are going to use has **full** access on the database AoWoW is going to occupy and ideally only **read** access on the world database you are going to reference. Import files 01 - 03 from `setup/sql/` in order into the AoWoW database `mysql -p {your-db-here} < setup/sql/01-db_structure.sql`, etc. +**Optional**: If you are using MySQL ≥ 8.4.0 and want to support fulltext search for locale zhCN, additionally import `setup/sql/04-db_optional_mysql_only.sql`. Enables this in settings after AoWoW has been set up. + #### 3. Server created files See to it, that the web server is able to write the following directories and their children. If they are missing, the setup will create them with appropriate permissions * `cache/` diff --git a/includes/cfg.class.php b/includes/cfg.class.php index ebdc64e0..b1b45fd2 100644 --- a/includes/cfg.class.php +++ b/includes/cfg.class.php @@ -473,6 +473,27 @@ class Cfg return true; } + + private static function logographic_ft_search(int|string $value, ?string &$msg = '') : bool + { + if (!$value) + return true; + + $ok = true; + foreach (['?_spell', '?_items', '?_objects', '?_creature', '?_quests'] as $tbl) + { + if (DB::Aowow()->selectRow('SHOW INDEX FROM ?# WHERE `column_name` = ? AND `index_type` = "FULLTEXT"', $tbl, 'name_loc4')) + continue; + + $ok = false; + $msg .= "\nNo fulltext index found on col: 'name_loc4'; tbl: '".$tbl."'."; + } + + if (!$ok) + $msg .= "\nCannot enable option.\n"; + + return $ok; + } } ?> diff --git a/includes/components/filter.class.php b/includes/components/filter.class.php index 0fa11740..15b13dda 100644 --- a/includes/components/filter.class.php +++ b/includes/components/filter.class.php @@ -64,6 +64,7 @@ abstract class Filter protected const PATTERN_CRV = '/[\p{C};:%\\\\]/ui'; protected const PATTERN_INT = '/\D/'; public const PATTERN_PARAM = '/^[\p{L}\p{Sm} \d\p{P}]+$/ui'; + public const PATTERN_FT = '/[^[:alpha:] \d_-]/iu'; // +-*<>@()~" have special meaning; ' seems to fuck up the search; other irregular cases? protected const ENUM_FACTION = array( 469, 1037, 1106, 529, 1012, 87, 21, 910, 609, 942, 909, 530, 69, 577, 930, 1068, 1104, 729, 369, 92, 54, 946, 67, 1052, 749, 47, 989, 1090, 1098, 978, 1011, 93, 1015, 1038, 76, 470, 349, 1031, 1077, 809, @@ -571,6 +572,10 @@ abstract class Filter if (!$string && $this->values['na']) $string = $this->values['na']; + // always allow sub 3 chars for logographic locales + if (Lang::getLocale()->isLogographic()) + $shortStr = true; + $qry = []; foreach ($fields as $f) { @@ -614,7 +619,11 @@ abstract class Filter if (!$string && $this->values['na']) $string = $this->values['na']; - $string = preg_replace('/[^[:alpha:] \d_-]/iu', ' ', $string); + // always allow sub 3 chars for logographic locales + if (Lang::getLocale()->isLogographic() && !Cfg::get('LOGOGRAPHIC_FT_SEARCH')) + return $this->tokenizeString($fields, $string, $exact, $shortStr); + + $string = trim(preg_replace(self::PATTERN_FT, ' ', $string)); if (!$string) return []; @@ -635,7 +644,7 @@ abstract class Filter // single cnd? if (!$qry) { - trigger_error('Filter::tokenizeString - could not tokenize string: '.$string, E_USER_NOTICE); + trigger_error('Filter::buildMatchLookup - could build MATCH AGAINST from: '.$string, E_USER_NOTICE); $this->error = true; } else if (count($qry) > 1) diff --git a/includes/components/search.class.php b/includes/components/search.class.php index bdeaa4e6..168cbbcc 100644 --- a/includes/components/search.class.php +++ b/includes/components/search.class.php @@ -79,6 +79,7 @@ class Search private array $resultStore = []; private array $included = []; private array $excluded = []; + private array $fulltext = []; private array $cndBase = ['AND']; private bool $idSearch = false; @@ -109,25 +110,32 @@ class Search foreach (explode(' ', $this->query) as $raw) { + // ivalid chars for both LIKE and MATCH $clean = str_replace(['\\', '%'], '', $raw); if ($clean === '') continue; - if ($clean[0] == '-') + $ex = ($clean[0] == '-'); + if ($ex) { - if (mb_strlen($clean) < 4 && !Lang::getLocale()->isLogographic()) - $this->invalid[] = mb_substr($raw, 1); - else - $this->excluded[] = mb_substr(str_replace('_', '\\_', $clean), 1); + $clean = mb_substr($clean, 1); + $raw = mb_substr($raw, 1); } - else + + if (mb_strlen($clean) < 3 && !Lang::getLocale()->isLogographic()) { - if (mb_strlen($clean) < 3 && !Lang::getLocale()->isLogographic()) - $this->invalid[] = $raw; - else - $this->included[] = str_replace('_', '\\_', $clean); + $this->invalid[] = $raw; + continue; } + + $this->{$ex ? 'excluded' : 'included'}[] = str_replace('_', '\\_', $clean); + + // note: a fulltext search purely with exclude tokens will return no result + if (($tokens = trim(preg_replace(Filter::PATTERN_FT, ' ', $clean))) !== '') + foreach (array_filter(explode(' ', $tokens)) as $t) + if (mb_strlen($t) > 2) + $this->fulltext[] = ($ex ? '-' : '+') . $t . '*'; } } @@ -176,21 +184,19 @@ class Search if ($this->idSearch && $this->included) return ['id', $this->included]; - if (!$this->included) + if (Lang::getLocale()->isLogographic() && !Cfg::get('LOGOGRAPHIC_FT_SEARCH')) + return $this->createLikeLookup($fields); + + if (!$this->fulltext) return []; // default to name-field if (!$fields) $fields[] = 'name_loc'.Lang::getLocale()->value; - $match = array_merge( - array_map(fn($x) => '+'.preg_replace('/[^[:alpha:] \d_-]/iu', ' ', $x).'*', $this->included), - array_map(fn($x) => '-'.preg_replace('/[^[:alpha:] \d_-]/iu', ' ', $x).'*', $this->excluded) - ); - $qry = []; foreach ($fields as $f) - $qry[] = [$f, $match, 'MATCH']; + $qry[] = [$f, $this->fulltext, 'MATCH']; // single cnd? if (count($qry) > 1) @@ -495,6 +501,8 @@ class Search { $miscData = ['calcTotal' => true]; $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; if ($this->moduleMask & self::TYPE_JSON) { @@ -574,11 +582,15 @@ class Search private function _searchAbility() : ?array // 7 Abilities (Player + Pet) $moduleMask & 0x0000080 { + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + $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->createMatchLookup() + $lookup )); $abilities = new SpellList($cnd, ['calcTotal' => true]); @@ -638,10 +650,11 @@ class Search private function _searchTalent() : ?array // 8 Talents (Player + Pet) $moduleMask & 0x0000100 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', [-7, -2]], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', [-7, -2]], $lookup]); $talents = new SpellList($cnd, ['calcTotal' => true]); $data = $talents->getListviewData(); @@ -697,10 +710,11 @@ class Search private function _searchGlyph() : ?array // 9 Glyphs $moduleMask & 0x0000200 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', -13], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', -13], $lookup]); $glyphs = new SpellList($cnd, ['calcTotal' => true]); $data = $glyphs->getListviewData(); @@ -751,10 +765,11 @@ class Search private function _searchProficiency() : ?array // 10 Proficiencies $moduleMask & 0x0000400 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', -11], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', -11], $lookup]); $prof = new SpellList($cnd, ['calcTotal' => true]); $data = $prof->getListviewData(); @@ -805,10 +820,11 @@ class Search private function _searchProfession() : ?array // 11 Professions (Primary + Secondary) $moduleMask & 0x0000800 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', [9, 11]], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', [9, 11]], $lookup]); $prof = new SpellList($cnd, ['calcTotal' => true]); $data = $prof->getListviewData(); @@ -859,10 +875,11 @@ class Search private function _searchCompanion() : ?array // 12 Companions $moduleMask & 0x0001000 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', -6], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', -6], $lookup]); $vPets = new SpellList($cnd, ['calcTotal' => true]); $data = $vPets->getListviewData(); @@ -913,10 +930,11 @@ class Search private function _searchMount() : ?array // 13 Mounts $moduleMask & 0x0002000 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', -5], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', -5], $lookup]); $mounts = new SpellList($cnd, ['calcTotal' => true]); $data = $mounts->getListviewData(); @@ -966,10 +984,14 @@ class Search private function _searchCreature() : ?array // 14 NPCs $moduleMask & 0x0004000 { + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + $cnd = array_merge($this->cndBase, array( [['flagsExtra', 0x80], 0], // exclude trigger creatures [['cuFlags', NPC_CU_DIFFICULTY_DUMMY, '&'], 0], // exclude difficulty entries - $this->createMatchLookup() + $lookup )); $npcs = new CreatureList($cnd, ['calcTotal' => true]); @@ -1019,9 +1041,13 @@ class Search private function _searchQuest() : ?array // 15 Quests $moduleMask & 0x0008000 { + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + $cnd = array_merge($this->cndBase, array( [['flags', CUSTOM_UNAVAILABLE | CUSTOM_DISABLED, '&'], 0], - $this->createMatchLookup() + $lookup )); $quests = new QuestList($cnd, ['calcTotal' => true]); @@ -1218,7 +1244,11 @@ class Search private function _searchObject() : ?array // 19 Objects $moduleMask & 0x0080000 { - $cnd = array_merge($this->cndBase, [$this->createMatchLookup()]); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [$lookup]); $objects = new GameObjectList($cnd, ['calcTotal' => true]); $data = $objects->getListviewData(); @@ -1378,10 +1408,11 @@ class Search private function _searchCreatureAbility() : ?array // 23 NPCAbilities $moduleMask & 0x0800000 { - $cnd = array_merge($this->cndBase, array( - ['s.typeCat', -8], - $this->createMatchLookup() - )); + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + + $cnd = array_merge($this->cndBase, [['s.typeCat', -8], $lookup]); $npcAbilities = new SpellList($cnd, ['calcTotal' => true]); $data = $npcAbilities->getListviewData(); @@ -1433,6 +1464,10 @@ class Search private function _searchSpell() : ?array // 24 Spells (Misc + GM + triggered abilities) $moduleMask & 0x1000000 { + $lookup = $this->createMatchLookup(); + if (!$lookup) + return null; + $cnd = array_merge($this->cndBase, array( ['s.typeCat', -8, '!'], [ @@ -1441,7 +1476,7 @@ class Search ['s.cuFlags', SPELL_CU_TRIGGERED, '&'], ['s.attributes0', 0x80, '&'] ], - $this->createMatchLookup() + $lookup )); $misc = new SpellList($cnd, ['calcTotal' => true]); diff --git a/setup/sql/01-db_structure.sql b/setup/sql/01-db_structure.sql index b9d4fba1..b9eba208 100644 --- a/setup/sql/01-db_structure.sql +++ b/setup/sql/01-db_structure.sql @@ -581,7 +581,6 @@ CREATE TABLE `aowow_creature` ( FULLTEXT `idx_name0` (`name_loc0`), FULLTEXT `idx_name2` (`name_loc2`), FULLTEXT `idx_name3` (`name_loc3`), - FULLTEXT `idx_name4` (`name_loc4`), FULLTEXT `idx_name6` (`name_loc6`), FULLTEXT `idx_name8` (`name_loc8`), KEY `idx_spell1` (`spell1`), @@ -1473,7 +1472,6 @@ CREATE TABLE `aowow_items` ( FULLTEXT `idx_name0` (`name_loc0`), FULLTEXT `idx_name2` (`name_loc2`), FULLTEXT `idx_name3` (`name_loc3`), - FULLTEXT `idx_name4` (`name_loc4`), FULLTEXT `idx_name6` (`name_loc6`), FULLTEXT `idx_name8` (`name_loc8`), KEY `idx_itemset` (`itemset`) @@ -1668,7 +1666,6 @@ CREATE TABLE `aowow_objects` ( FULLTEXT `idx_name0` (`name_loc0`), FULLTEXT `idx_name2` (`name_loc2`), FULLTEXT `idx_name3` (`name_loc3`), - FULLTEXT `idx_name4` (`name_loc4`), FULLTEXT `idx_name6` (`name_loc6`), FULLTEXT `idx_name8` (`name_loc8`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; @@ -2272,7 +2269,6 @@ CREATE TABLE `aowow_quests` ( FULLTEXT `idx_name0` (`name_loc0`), FULLTEXT `idx_name2` (`name_loc2`), FULLTEXT `idx_name3` (`name_loc3`), - FULLTEXT `idx_name4` (`name_loc4`), FULLTEXT `idx_name6` (`name_loc6`), FULLTEXT `idx_name8` (`name_loc8`), KEY `idx_sourcespell` (`sourceSpellId`), @@ -2881,7 +2877,6 @@ CREATE TABLE `aowow_spell` ( FULLTEXT `idx_name0` (`name_loc0`), FULLTEXT `idx_name2` (`name_loc2`), FULLTEXT `idx_name3` (`name_loc3`), - FULLTEXT `idx_name4` (`name_loc4`), FULLTEXT `idx_name6` (`name_loc6`), FULLTEXT `idx_name8` (`name_loc8`), KEY `idx_spellfamily` (`spellFamilyId`), diff --git a/setup/sql/02-db_initial_data.sql b/setup/sql/02-db_initial_data.sql index addd6536..bb1774fe 100644 --- a/setup/sql/02-db_initial_data.sql +++ b/setup/sql/02-db_initial_data.sql @@ -61,7 +61,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_config` WRITE; /*!40000 ALTER TABLE `aowow_config` DISABLE KEYS */; -INSERT INTO `aowow_config` VALUES ('acc_allow_register','1','1',3,132,'allow/disallow account creation (requires AUTH_MODE: aowow)'),('acc_auth_mode','0','0',3,1425,'source to auth against - 0:AoWoW, 1:TC auth-table, 2:External script (config/extAuth.php)'),('acc_create_save_decay','604800','604800',3,129,'time in wich an unconfirmed account cannot be overwritten by new registrations'),('acc_ext_create_url','',NULL,3,136,'if auth mode is not self; link to external account creation'),('acc_ext_recover_url','',NULL,3,136,'if auth mode is not self; link to external account recovery'),('acc_failed_auth_block','900','15 * 60',3,129,'how long an account is closed after exceeding FAILED_AUTH_COUNT (in seconds)'),('acc_failed_auth_count','5','5',3,129,'how often invalid passwords are tolerated'),('acc_max_avatar_uploads','10','10',3,129,'premium users may upload this many avatars'),('acc_recovery_decay','300','300',3,129,'time to recover your account and new recovery requests are blocked'),('acc_rename_decay','2592000','30 * 24 * 60 * 60',3,129,'delay between username changes'),('battlegroup','Pure Pwnage',NULL,1,136,'pretend, we belong to a battlegroup to satisfy profiler-related javascripts'),('board_url','http://www.wowhead.com/forums?board=',NULL,1,136,'another halfbaked javascript thing..'),('cache_decay','25200','60 * 60 * 7',2,129,'time to keep cache in seconds'),('cache_dir','','cache/template',2,136,'generated pages are saved here (requires CACHE_MODE: filecache)'),('cache_mode','1','1',2,1185,'set cache method - 0:filecache, 1:memcached'),('contact_email','feedback@aowow.org',NULL,1,136,'displayed sender for auth-mails, ect'),('debug','0','0',1,145,'disable cache, enable error_reporting - 0:None, 1:Error, 2:Warning, 3:Info'),('default_charset','utf-8',NULL,0,72,''),('force_ssl','0','0',1,132,'enforce SSL, if auto-detect fails'),('gtag_measurement_id','',NULL,6,136,'enter your Google Tag measurement ID here to track site stats'),('locales','349','0x15D',1,1441,'allowed locales - 0:English, 2:French, 3:German, 4:Chinese, 6:Spanish, 8:Russian'),('maintenance','1','0',1,132,'display brb gnomes and block access for non-staff'),('memory_limit','1500M','1500M',0,200,'parsing spell.dbc is quite intense'),('name','Aowow Database Viewer (ADV)',NULL,1,136,'website title'),('name_short','Aowow',NULL,1,136,'feed title'),('profiler_enable','0','0',7,1412,'enable/disable profiler feature'),('profiler_queue_delay','3000','3000',7,129,'min. delay between queue cycles (in ms)'),('profiler_resync_delay','3600','1 * 60 * 60',7,129,'how often a character can be refreshed (in sec)'),('profiler_resync_ping','5000','5000',7,129,'how often the javascript asks for for updates, when queued (in ms)'),('rep_req_border_epic','15000','15000',5,129,'required reputation for epic quality avatar border'),('rep_req_border_legendary','25000','25000',5,129,'required reputation for legendary quality avatar border'),('rep_req_border_rare','10000','10000',5,129,'required reputation for rare quality avatar border'),('rep_req_border_uncommon','5000','5000',5,129,'required reputation for uncommon quality avatar border'),('rep_req_comment','75','75',5,129,'required reputation to write a comment'),('rep_req_downvote','250','250',5,129,'required reputation to downvote comments'),('rep_req_ext_links','150','150',5,129,'required reputation to link to external sites'),('rep_req_premium','25000','25000',5,129,'required reputation for premium status through reputation'),('rep_req_reply','75','75',5,129,'required reputation to write a reply'),('rep_req_supervote','2500','2500',5,129,'required reputation for double vote effect'),('rep_req_upvote','125','125',5,129,'required reputation to upvote comments'),('rep_req_votemore_add','250','250',5,129,'required reputation per additional vote past threshold'),('rep_req_votemore_base','2000','2000',5,129,'gains more votes past this threshold'),('rep_reward_article','100','100',5,129,'submitted an approved article/guide'),('rep_reward_bad_report','0','0',5,129,'filed a rejected report'),('rep_reward_comment','1','1',5,129,'created a comment (not a reply)'),('rep_reward_dailyvisit','5','5',5,129,'daily visit'),('rep_reward_downvoted','0','0',5,129,'comment received downvote'),('rep_reward_good_report','10','10',5,129,'filed an accepted report'),('rep_reward_register','100','100',5,129,'activated an account'),('rep_reward_submit_screenshot','10','10',5,129,'uploaded screenshot was approved'),('rep_reward_suggest_video','10','10',5,129,'suggested video was approved'),('rep_reward_upvoted','5','5',5,129,'comment received upvote'),('rep_reward_user_suspended','-200','-200',5,129,'moderator revoked rights'),('rep_reward_user_warned','-50','-50',5,129,'moderator imposed a warning'),('screenshot_min_size','200','200',1,1153,'minimum dimensions of uploaded screenshots in px (yes, it\'s square, no it cant go below 200)'),('serialize_precision','5',NULL,0,65,''),('session_cache_dir','',NULL,4,136,'php sessions are saved here. Leave empty to use php default directory.'),('session_timeout_delay','3600','60 * 60',4,129,'non-permanent session times out in time() + X'),('session.gc_divisor','100','100',4,200,'probability to remove session data on garbage collection'),('session.gc_maxlifetime','604800','7 * 24 * 60 * 60',4,200,'lifetime of session data'),('session.gc_probability','1','0',4,200,'probability to remove session data on garbage collection'),('site_host','',NULL,1,904,'points js to executable files'),('static_host','',NULL,1,904,'points js to images & scripts'),('ttl_rss','60','60',1,129,'time to live for RSS (in seconds)'),('ua_measurement_key','',NULL,6,136,'[DEPRECATED ?] Enter your Google Universal Analytics key here to track site stats'),('user_max_votes','50','50',1,129,'vote limit per day'); +INSERT INTO `aowow_config` VALUES ('logographic_ft_search','0','0',1,1156,'enables fulltext search for logographic languages (CN, KR, TW). The database MUST support this (i.e. MySQL implements ngram)'),('acc_allow_register','1','1',3,132,'allow/disallow account creation (requires AUTH_MODE: aowow)'),('acc_auth_mode','0','0',3,1425,'source to auth against - 0:AoWoW, 1:TC auth-table, 2:External script (config/extAuth.php)'),('acc_create_save_decay','604800','604800',3,129,'time in wich an unconfirmed account cannot be overwritten by new registrations'),('acc_ext_create_url','',NULL,3,136,'if auth mode is not self; link to external account creation'),('acc_ext_recover_url','',NULL,3,136,'if auth mode is not self; link to external account recovery'),('acc_failed_auth_block','900','15 * 60',3,129,'how long an account is closed after exceeding FAILED_AUTH_COUNT (in seconds)'),('acc_failed_auth_count','5','5',3,129,'how often invalid passwords are tolerated'),('acc_max_avatar_uploads','10','10',3,129,'premium users may upload this many avatars'),('acc_recovery_decay','300','300',3,129,'time to recover your account and new recovery requests are blocked'),('acc_rename_decay','2592000','30 * 24 * 60 * 60',3,129,'delay between username changes'),('battlegroup','Pure Pwnage',NULL,1,136,'pretend, we belong to a battlegroup to satisfy profiler-related javascripts'),('board_url','http://www.wowhead.com/forums?board=',NULL,1,136,'another halfbaked javascript thing..'),('cache_decay','25200','60 * 60 * 7',2,129,'time to keep cache in seconds'),('cache_dir','','cache/template',2,136,'generated pages are saved here (requires CACHE_MODE: filecache)'),('cache_mode','1','1',2,1185,'set cache method - 0:filecache, 1:memcached'),('contact_email','feedback@aowow.org',NULL,1,136,'displayed sender for auth-mails, ect'),('debug','0','0',1,145,'disable cache, enable error_reporting - 0:None, 1:Error, 2:Warning, 3:Info'),('default_charset','utf-8',NULL,0,72,''),('force_ssl','0','0',1,132,'enforce SSL, if auto-detect fails'),('gtag_measurement_id','',NULL,6,136,'enter your Google Tag measurement ID here to track site stats'),('locales','349','0x15D',1,1441,'allowed locales - 0:English, 2:French, 3:German, 4:Chinese, 6:Spanish, 8:Russian'),('maintenance','1','0',1,132,'display brb gnomes and block access for non-staff'),('memory_limit','1500M','1500M',0,200,'parsing spell.dbc is quite intense'),('name','Aowow Database Viewer (ADV)',NULL,1,136,'website title'),('name_short','Aowow',NULL,1,136,'feed title'),('profiler_enable','0','0',7,1412,'enable/disable profiler feature'),('profiler_queue_delay','3000','3000',7,129,'min. delay between queue cycles (in ms)'),('profiler_resync_delay','3600','1 * 60 * 60',7,129,'how often a character can be refreshed (in sec)'),('profiler_resync_ping','5000','5000',7,129,'how often the javascript asks for for updates, when queued (in ms)'),('rep_req_border_epic','15000','15000',5,129,'required reputation for epic quality avatar border'),('rep_req_border_legendary','25000','25000',5,129,'required reputation for legendary quality avatar border'),('rep_req_border_rare','10000','10000',5,129,'required reputation for rare quality avatar border'),('rep_req_border_uncommon','5000','5000',5,129,'required reputation for uncommon quality avatar border'),('rep_req_comment','75','75',5,129,'required reputation to write a comment'),('rep_req_downvote','250','250',5,129,'required reputation to downvote comments'),('rep_req_ext_links','150','150',5,129,'required reputation to link to external sites'),('rep_req_premium','25000','25000',5,129,'required reputation for premium status through reputation'),('rep_req_reply','75','75',5,129,'required reputation to write a reply'),('rep_req_supervote','2500','2500',5,129,'required reputation for double vote effect'),('rep_req_upvote','125','125',5,129,'required reputation to upvote comments'),('rep_req_votemore_add','250','250',5,129,'required reputation per additional vote past threshold'),('rep_req_votemore_base','2000','2000',5,129,'gains more votes past this threshold'),('rep_reward_article','100','100',5,129,'submitted an approved article/guide'),('rep_reward_bad_report','0','0',5,129,'filed a rejected report'),('rep_reward_comment','1','1',5,129,'created a comment (not a reply)'),('rep_reward_dailyvisit','5','5',5,129,'daily visit'),('rep_reward_downvoted','0','0',5,129,'comment received downvote'),('rep_reward_good_report','10','10',5,129,'filed an accepted report'),('rep_reward_register','100','100',5,129,'activated an account'),('rep_reward_submit_screenshot','10','10',5,129,'uploaded screenshot was approved'),('rep_reward_suggest_video','10','10',5,129,'suggested video was approved'),('rep_reward_upvoted','5','5',5,129,'comment received upvote'),('rep_reward_user_suspended','-200','-200',5,129,'moderator revoked rights'),('rep_reward_user_warned','-50','-50',5,129,'moderator imposed a warning'),('screenshot_min_size','200','200',1,1153,'minimum dimensions of uploaded screenshots in px (yes, it\'s square, no it cant go below 200)'),('serialize_precision','5',NULL,0,65,''),('session_cache_dir','',NULL,4,136,'php sessions are saved here. Leave empty to use php default directory.'),('session_timeout_delay','3600','60 * 60',4,129,'non-permanent session times out in time() + X'),('session.gc_divisor','100','100',4,200,'probability to remove session data on garbage collection'),('session.gc_maxlifetime','604800','7 * 24 * 60 * 60',4,200,'lifetime of session data'),('session.gc_probability','1','0',4,200,'probability to remove session data on garbage collection'),('site_host','',NULL,1,904,'points js to executable files'),('static_host','',NULL,1,904,'points js to images & scripts'),('ttl_rss','60','60',1,129,'time to live for RSS (in seconds)'),('ua_measurement_key','',NULL,6,136,'[DEPRECATED ?] Enter your Google Universal Analytics key here to track site stats'),('user_max_votes','50','50',1,129,'vote limit per day'); /*!40000 ALTER TABLE `aowow_config` ENABLE KEYS */; UNLOCK TABLES; @@ -71,7 +71,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_dbversion` WRITE; /*!40000 ALTER TABLE `aowow_dbversion` DISABLE KEYS */; -INSERT INTO `aowow_dbversion` VALUES (1768556689,0,NULL,NULL); +INSERT INTO `aowow_dbversion` VALUES (1768672800,0,NULL,NULL); /*!40000 ALTER TABLE `aowow_dbversion` ENABLE KEYS */; UNLOCK TABLES; diff --git a/setup/sql/04-db_optional_mysql_only.sql b/setup/sql/04-db_optional_mysql_only.sql new file mode 100644 index 00000000..51f2003a --- /dev/null +++ b/setup/sql/04-db_optional_mysql_only.sql @@ -0,0 +1,5 @@ +ALTER TABLE `aowow_creature` ADD FULLTEXT `idx_name4` (`name_loc4`) WITH PARSER ngram; +ALTER TABLE `aowow_items` ADD FULLTEXT `idx_name4` (`name_loc4`) WITH PARSER ngram; +ALTER TABLE `aowow_objects` ADD FULLTEXT `idx_name4` (`name_loc4`) WITH PARSER ngram; +ALTER TABLE `aowow_quests` ADD FULLTEXT `idx_name4` (`name_loc4`) WITH PARSER ngram; +ALTER TABLE `aowow_spell` ADD FULLTEXT `idx_name4` (`name_loc4`) WITH PARSER ngram; diff --git a/setup/sql/updates/1768672799_01.sql b/setup/sql/updates/1768672799_01.sql new file mode 100644 index 00000000..140bf2d6 --- /dev/null +++ b/setup/sql/updates/1768672799_01.sql @@ -0,0 +1,16 @@ +ALTER TABLE `aowow_creature` DROP INDEX `idx_name4`; +ALTER TABLE `aowow_items` DROP INDEX `idx_name4`; +ALTER TABLE `aowow_objects` DROP INDEX `idx_name4`; +ALTER TABLE `aowow_quests` DROP INDEX `idx_name4`; +ALTER TABLE `aowow_spell` DROP INDEX `idx_name4`; + +SET SESSION innodb_ft_enable_stopword = OFF; + +OPTIMIZE TABLE `aowow_spell`; +OPTIMIZE TABLE `aowow_quests`; +OPTIMIZE TABLE `aowow_creature`; +OPTIMIZE TABLE `aowow_items`; +OPTIMIZE TABLE `aowow_objects`; + +REPLACE INTO `aowow_config` VALUES + ('logographic_ft_search', '0', '0', 1, 0x484, 'enables fulltext search for logographic languages (CN, KR, TW). The database MUST support this (i.e. MySQL implements ngram)'); diff --git a/setup/tools/sqlgen/creature.ss.php b/setup/tools/sqlgen/creature.ss.php index 5448ea10..9691b5a0 100644 --- a/setup/tools/sqlgen/creature.ss.php +++ b/setup/tools/sqlgen/creature.ss.php @@ -102,8 +102,10 @@ CLISetup::registerSetup("sql", new class extends SetupScript { WHERE ct.entry IN (?a) } LIMIT ?d, ?d'; - $i = 0; DB::Aowow()->query('TRUNCATE ?_creature'); + DB::Aowow()->query('SET SESSION innodb_ft_enable_stopword = OFF'); + + $i = 0; while ($npcs = DB::World()->select($baseQuery, NPC_CU_INSTANCE_BOSS, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) { CLI::write(' * batch #' . ++$i . ' (' . count($npcs) . ')', CLI::LOG_BLANK, true, true); diff --git a/setup/tools/sqlgen/items.ss.php b/setup/tools/sqlgen/items.ss.php index b2a22193..0013fc99 100644 --- a/setup/tools/sqlgen/items.ss.php +++ b/setup/tools/sqlgen/items.ss.php @@ -130,8 +130,10 @@ CLISetup::registerSetup("sql", new class extends SetupScript { WHERE it.entry IN (?a) } LIMIT ?d, ?d'; - $i = 0; DB::Aowow()->query('TRUNCATE ?_items'); + DB::Aowow()->query('SET SESSION innodb_ft_enable_stopword = OFF'); + + $i = 0; while ($items = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) { CLI::write(' * batch #' . ++$i . ' (' . count($items) . ')', CLI::LOG_BLANK, true, true); diff --git a/setup/tools/sqlgen/objects.ss.php b/setup/tools/sqlgen/objects.ss.php index 3fae3d18..66d5d77f 100644 --- a/setup/tools/sqlgen/objects.ss.php +++ b/setup/tools/sqlgen/objects.ss.php @@ -67,8 +67,10 @@ CLISetup::registerSetup("sql", new class extends SetupScript GROUP BY go.entry LIMIT ?d, ?d'; - $i = 0; DB::Aowow()->query('TRUNCATE ?_objects'); + DB::Aowow()->query('SET SESSION innodb_ft_enable_stopword = OFF'); + + $i = 0; while ($objects = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) { CLI::write(' * batch #' . ++$i . ' (' . count($objects) . ')', CLI::LOG_BLANK, true, true); diff --git a/setup/tools/sqlgen/quests.ss.php b/setup/tools/sqlgen/quests.ss.php index 7f56eb00..a2e7348d 100644 --- a/setup/tools/sqlgen/quests.ss.php +++ b/setup/tools/sqlgen/quests.ss.php @@ -116,8 +116,10 @@ CLISetup::registerSetup("sql", new class extends SetupScript { WHERE q.Id IN (?a) } LIMIT ?d, ?d'; - $i = 0; DB::Aowow()->query('TRUNCATE ?_quests'); + DB::Aowow()->query('SET SESSION innodb_ft_enable_stopword = OFF'); + + $i = 0; while ($quests = DB::World()->select($baseQuery, $ids ?: DBSIMPLE_SKIP, CLISetup::SQL_BATCH * $i, CLISetup::SQL_BATCH)) { CLI::write(' * batch #' . ++$i . ' (' . count($quests) . ')', CLI::LOG_BLANK, true, true); diff --git a/setup/tools/sqlgen/spell.ss.php b/setup/tools/sqlgen/spell.ss.php index 1f24e1de..cbe69f31 100644 --- a/setup/tools/sqlgen/spell.ss.php +++ b/setup/tools/sqlgen/spell.ss.php @@ -199,6 +199,7 @@ CLISetup::registerSetup("sql", new class extends SetupScript DB::Aowow()->query('TRUNCATE ?_spell'); + DB::Aowow()->query('SET SESSION innodb_ft_enable_stopword = OFF'); // merge serverside spells into aowow_spell $lastMax = 0; diff --git a/template/pages/admin/siteconfig.tpl.php b/template/pages/admin/siteconfig.tpl.php index 048355ba..5437a838 100644 --- a/template/pages/admin/siteconfig.tpl.php +++ b/template/pages/admin/siteconfig.tpl.php @@ -18,7 +18,7 @@ var a = $WH.ce('a'); a.style.opacity = 0; a.className = errTxt ? 'icon-report' : 'icon-tick'; - g_addTooltip(a, errTxt || 'success', 'q'); + g_addTooltip(a, errTxt ? '
' + errTxt + '
' : 'success', 'q'); a.onclick = fadeout.bind(a); setTimeout(function () { $(a).animate({ opacity: '1.0' }, 250); }, 50); setTimeout(fadeout.bind(a), 10000);