diff --git a/includes/config.class.php b/includes/config.class.php index 2b30c75d..3f92b2f1 100644 --- a/includes/config.class.php +++ b/includes/config.class.php @@ -42,7 +42,8 @@ class Cfg private const IDX_DEFAULT = 3; private const IDX_COMMENT = 4; - private static $store = []; // name => [value, flags, cat, default, comment] + private static $store = []; // name => [value, flags, cat, default, comment] + private static $isLoaded = false; private static $rebuildScripts = array( // 'rep_req_border_unco' => ['global'], // currently not a template or buildScript @@ -70,20 +71,20 @@ class Cfg if ($err = self::validate($value, $flags, $comment)) { - trigger_error('Aowow config '.strtoupper($key).' failed validation and was skipped: '.$err, E_USER_ERROR); + self::throwError('Aowow config '.strtoupper($key).' failed validation and was skipped: '.$err); continue; } if ($flags & self::FLAG_INTERNAL) { - trigger_error('Aowow config '.strtoupper($key).' is flagged as internaly generated and should not have been set in DB.', E_USER_ERROR); + self::throwError('Aowow config '.strtoupper($key).' is flagged as internaly generated and should not have been set in DB.'); continue; } if ($flags & self::FLAG_ON_LOAD_FN) { if (!method_exists('Cfg', $key)) - trigger_error('Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set', E_USER_WARNING); + self::throwError('Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set'); else self::{$key}($value); } @@ -93,10 +94,21 @@ class Cfg self::$store[strtolower($key)] = [$value, $flags, $catg, $default, $comment]; } + + if (CLI && !count(self::$store)) + { + CLI::write('Cfg::load - aowow_config unexpectedly empty.', CLI::LOG_WARN); + return; + } + + self::$isLoaded = true; } public static function add(string $key, /*int|string*/ $value) : string { + if (!self::$isLoaded) + return 'used add() on uninitialized config'; + if (!$key) return 'empty option name given'; @@ -124,6 +136,9 @@ class Cfg public static function delete(string $key) : string { + if (!self::$isLoaded) + return 'used delete() on uninitialized config'; + $key = strtolower($key); if (!isset(self::$store[$key])) @@ -151,7 +166,9 @@ class Cfg if (!isset(self::$store[$key])) { - trigger_error('cfg not defined: '.$key, E_USER_ERROR); + if (self::$isLoaded) + self::throwError('cfg not defined: '.strtoupper($key)); + return ''; } @@ -167,6 +184,9 @@ class Cfg public static function set(string $key, /*int|string*/ $value, ?array &$rebuildFiles = []) : string { + if (!self::$isLoaded) + return 'used set() on uninitialized config'; + $key = strtolower($key); if (!isset(self::$store[$key])) @@ -202,7 +222,6 @@ class Cfg DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $oldValue, $key); self::$store[$key][self::IDX_VALUE] = $oldValue; - // trigger_error($errMsg) ? return $errMsg; } } @@ -221,12 +240,15 @@ class Cfg public static function reset(string $key, ?array &$rebuildFiles = []) : string { + if (!self::$isLoaded) + return 'used reset() on uninitialized config'; + $key = strtolower($key); if (!isset(self::$store[$key])) return 'configuration option not found'; - [, $flags, , $default, ] = self::$store[$key]; + [$oldValue, $flags, , $default, ] = self::$store[$key]; if ($flags & self::FLAG_INTERNAL) return 'can\'t set internal options directly'; @@ -240,6 +262,25 @@ class Cfg DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $default, $key); self::$store[$key][self::IDX_VALUE] = $default; + // validate change + if ($flags & self::FLAG_ON_SET_FN) + { + $errMsg = ''; + if (!method_exists('Cfg', $key)) + $errMsg = 'required onSetFN validator not set'; + else + self::{$key}($default, $errMsg); + + if ($errMsg) + { + // rollback change + DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $oldValue, $key); + self::$store[$key][self::IDX_VALUE] = $oldValue; + + return $errMsg; + } + } + // trigger setup build return self::handleFileBuild($key, $rebuildFiles); } @@ -333,7 +374,32 @@ class Cfg return $msg; } - private static function acc_auth_mode(/*int|string*/ $value, ?string $msg = '') : bool + private static function throwError($msg) : void + { + if (CLI) + CLI::write($msg, CLI::LOG_ERROR); + else + trigger_error($msg, E_USER_ERROR); + } + + private static function locales(/*int|string*/ $value, ?string &$msg = '') : bool + { + if (!CLI) + return true; + + CLISetup::$localeIds = []; + foreach (CLISetup::$locales as $idx => $_) + if (!($value) || ($value & (1 << $idx))) + CLISetup::$localeIds[] = $idx; + + if (!empty(CLISetup::$localeIds)) + return true; + + $msg .= 'no valid locales set'; + return false; + } + + private static function acc_auth_mode(/*int|string*/ $value, ?string &$msg = '') : bool { if ($value == 1 && !extension_loaded('gmp')) { @@ -344,7 +410,7 @@ class Cfg return true; } - private static function profiler_enable(/*int|string*/ $value, ?string $msg = '') : bool + private static function profiler_enable(/*int|string*/ $value, ?string &$msg = '') : bool { if ($value != 1) return true; @@ -352,7 +418,7 @@ class Cfg return Profiler::queueStart($msg); } - private static function static_host(/*int|string*/ $value, ?string $msg = '') : bool + private static function static_host(/*int|string*/ $value, ?string &$msg = '') : bool { self::$store['static_url'] = array( // points js to images & scripts (self::useSSL() ? 'https://' : 'http://').$value, @@ -365,7 +431,7 @@ class Cfg return true; } - private static function site_host(/*int|string*/ $value, ?string $msg = '') : bool + private static function site_host(/*int|string*/ $value, ?string &$msg = '') : bool { self::$store['host_url'] = array( // points js to executable files (self::useSSL() ? 'https://' : 'http://').$value, diff --git a/includes/utilities.php b/includes/utilities.php index c19a68eb..907263db 100644 --- a/includes/utilities.php +++ b/includes/utilities.php @@ -171,8 +171,7 @@ abstract class CLI continue; } - if (!$nCols) - $nCols = count($row); + $nCols = max($nCols, count($row)); for ($j = 0; $j < $nCols - 1; $j++) // don't pad last column $pads[$j] = max($pads[$j] ?? 0, mb_strlen($row[$j] ?? '')); diff --git a/setup/db_structure.sql b/setup/db_structure.sql index c550b65c..89f400cc 100644 --- a/setup/db_structure.sql +++ b/setup/db_structure.sql @@ -3218,7 +3218,7 @@ UNLOCK TABLES; LOCK TABLES `aowow_config` WRITE; /*!40000 ALTER TABLE `aowow_config` DISABLE KEYS */; -INSERT INTO `aowow_config` VALUES ('sql_limit_search','500','500',1,129,'max results for search'),('sql_limit_default','300','300',1,129,'max results for listviews'),('sql_limit_quicksearch','10','10',1,129,'max results for suggestions'),('sql_limit_none','0','0',1,129,'unlimited results (i wouldn\'t change that mate)'),('ttl_rss','60','60',1,129,'time to live for RSS (in seconds)'),('name','Aowow Database Viewer (ADV)',NULL,1,136,'website title'),('name_short','Aowow',NULL,1,136,'feed title'),('board_url','http://www.wowhead.com/forums?board=',NULL,1,136,'another halfbaked javascript thing..'),('contact_email','feedback@aowow.org',NULL,1,136,'displayed sender for auth-mails, ect'),('battlegroup','Pure Pwnage',NULL,1,136,'pretend, we belong to a battlegroup to satisfy profiler-related javascripts'),('debug','0','0',1,145,'disable cache, enable error_reporting - 0:None, 1:Error, 2:Warning, 3:Info'),('maintenance','1','0',1,132,'display brb gnomes and block access for non-staff'),('user_max_votes','50','50',1,129,'vote limit per day'),('force_ssl','0','0',1,132,'enforce SSL, if auto-detect fails'),('locales','349','0x15D',1,161,'allowed locales - 0:English, 2:French, 3:German, 4:Chinese, 6:Spanish, 8:Russian'),('screenshot_min_size','200','200',1,129,'minimum dimensions of uploaded screenshots in px (yes, it\'s square)'),('site_host','',NULL,1,904,'points js to executable files'),('static_host','',NULL,1,904,'points js to images & scripts'),('cache_decay','25200','60 * 60 * 7',2,129,'time to keep cache in seconds'),('cache_mode','1','1',2,161,'set cache method - 0:filecache, 1:memcached'),('cache_dir','','cache/template',2,136,'generated pages are saved here (requires CACHE_MODE: filecache)'),('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_allow_register','1','1',3,132,'allow/disallow account creation (requires AUTH_MODE: aowow)'),('acc_auth_mode','0','0',3,401,'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_recovery_decay','300','300',3,129,'time to recover your account and new recovery requests are blocked'),('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'),('session_timeout_delay','3600','60 * 60',4,129,'non-permanent session times out in time() + X'),('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'),('session.gc_divisor','100','100',4,200,'probability to remove session data on garbage collection'),('session_cache_dir','',NULL,4,136,'php sessions are saved here. Leave empty to use php default directory.'),('rep_req_upvote','125','125',5,129,'required reputation to upvote comments'),('rep_req_downvote','250','250',5,129,'required reputation to downvote comments'),('rep_req_comment','75','75',5,129,'required reputation to write a comment'),('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_votemore_base','2000','2000',5,129,'gains more votes past this threshold'),('rep_reward_register','100','100',5,129,'activated an account'),('rep_reward_upvoted','5','5',5,129,'comment received upvote'),('rep_reward_downvoted','0','0',5,129,'comment received downvote'),('rep_reward_good_report','10','10',5,129,'filed an accepted report'),('rep_reward_bad_report','0','0',5,129,'filed a rejected report'),('rep_reward_dailyvisit','5','5',5,129,'daily visit'),('rep_reward_user_warned','-50','-50',5,129,'moderator imposed a warning'),('rep_reward_comment','1','1',5,129,'created a comment (not a reply)'),('rep_req_premium','25000','25000',5,129,'required reputation for premium status through reputation'),('rep_reward_upload','10','10',5,129,'suggested / uploaded video / screenshot was approved'),('rep_reward_article','100','100',5,129,'submitted an approved article/guide'),('rep_reward_user_suspended','-200','-200',5,129,'moderator revoked rights'),('rep_req_votemore_add','250','250',5,129,'required reputation per additional vote past threshold'),('serialize_precision','5',NULL,0,65,'some derelict code, probably unused'),('memory_limit','1500M','1500M',0,200,'parsing spell.dbc is quite intense'),('default_charset','UTF-8','UTF-8',0,72,'default: UTF-8'),('analytics_user','',NULL,6,136,'enter your GA-user here to track site stats'),('profiler_enable','0','0',7,388,'enable/disable profiler feature'),('profiler_queue_delay','3000','3000',7,129,'min. delay between queue cycles (in ms)'),('profiler_resync_ping','5000','5000',7,129,'how often the javascript asks for for updates, when queued (in ms)'),('profiler_resync_delay','3600','1 * 60 * 60',7,129,'how often a character can be refreshed (in sec)'),('rep_req_border_unco','5000','5000',5,129,'required reputation for uncommon quality avatar border'),('rep_req_border_rare','10000','10000',5,129,'required reputation for rare quality avatar border'),('rep_req_border_epic','15000','15000',5,129,'required reputation for epic quality avatar border'),('rep_req_border_lege','25000','25000',5,129,'required reputation for legendary quality avatar border'); +INSERT INTO `aowow_config` VALUES ('sql_limit_search','500','500',1,129,'max results for search'),('sql_limit_default','300','300',1,129,'max results for listviews'),('sql_limit_quicksearch','10','10',1,129,'max results for suggestions'),('sql_limit_none','0','0',1,129,'unlimited results (i wouldn\'t change that mate)'),('ttl_rss','60','60',1,129,'time to live for RSS (in seconds)'),('name','Aowow Database Viewer (ADV)',NULL,1,136,'website title'),('name_short','Aowow',NULL,1,136,'feed title'),('board_url','http://www.wowhead.com/forums?board=',NULL,1,136,'another halfbaked javascript thing..'),('contact_email','feedback@aowow.org',NULL,1,136,'displayed sender for auth-mails, ect'),('battlegroup','Pure Pwnage',NULL,1,136,'pretend, we belong to a battlegroup to satisfy profiler-related javascripts'),('debug','0','0',1,145,'disable cache, enable error_reporting - 0:None, 1:Error, 2:Warning, 3:Info'),('maintenance','1','0',1,132,'display brb gnomes and block access for non-staff'),('user_max_votes','50','50',1,129,'vote limit per day'),('force_ssl','0','0',1,132,'enforce SSL, if auto-detect fails'),('locales','349','0x15D',1,1185,'allowed locales - 0:English, 2:French, 3:German, 4:Chinese, 6:Spanish, 8:Russian'),('screenshot_min_size','200','200',1,129,'minimum dimensions of uploaded screenshots in px (yes, it\'s square)'),('site_host','',NULL,1,904,'points js to executable files'),('static_host','',NULL,1,904,'points js to images & scripts'),('cache_decay','25200','60 * 60 * 7',2,129,'time to keep cache in seconds'),('cache_mode','1','1',2,161,'set cache method - 0:filecache, 1:memcached'),('cache_dir','','cache/template',2,136,'generated pages are saved here (requires CACHE_MODE: filecache)'),('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_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_recovery_decay','300','300',3,129,'time to recover your account and new recovery requests are blocked'),('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'),('session_timeout_delay','3600','60 * 60',4,129,'non-permanent session times out in time() + X'),('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'),('session.gc_divisor','100','100',4,200,'probability to remove session data on garbage collection'),('session_cache_dir','',NULL,4,136,'php sessions are saved here. Leave empty to use php default directory.'),('rep_req_upvote','125','125',5,129,'required reputation to upvote comments'),('rep_req_downvote','250','250',5,129,'required reputation to downvote comments'),('rep_req_comment','75','75',5,129,'required reputation to write a comment'),('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_votemore_base','2000','2000',5,129,'gains more votes past this threshold'),('rep_reward_register','100','100',5,129,'activated an account'),('rep_reward_upvoted','5','5',5,129,'comment received upvote'),('rep_reward_downvoted','0','0',5,129,'comment received downvote'),('rep_reward_good_report','10','10',5,129,'filed an accepted report'),('rep_reward_bad_report','0','0',5,129,'filed a rejected report'),('rep_reward_dailyvisit','5','5',5,129,'daily visit'),('rep_reward_user_warned','-50','-50',5,129,'moderator imposed a warning'),('rep_reward_comment','1','1',5,129,'created a comment (not a reply)'),('rep_req_premium','25000','25000',5,129,'required reputation for premium status through reputation'),('rep_reward_upload','10','10',5,129,'suggested / uploaded video / screenshot was approved'),('rep_reward_article','100','100',5,129,'submitted an approved article/guide'),('rep_reward_user_suspended','-200','-200',5,129,'moderator revoked rights'),('rep_req_votemore_add','250','250',5,129,'required reputation per additional vote past threshold'),('serialize_precision','5',NULL,0,65,'some derelict code, probably unused'),('memory_limit','1500M','1500M',0,200,'parsing spell.dbc is quite intense'),('default_charset','UTF-8','UTF-8',0,72,'default: UTF-8'),('analytics_user','',NULL,6,136,'enter your GA-user here to track site stats'),('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_ping','5000','5000',7,129,'how often the javascript asks for for updates, when queued (in ms)'),('profiler_resync_delay','3600','1 * 60 * 60',7,129,'how often a character can be refreshed (in sec)'),('rep_req_border_unco','5000','5000',5,129,'required reputation for uncommon quality avatar border'),('rep_req_border_rare','10000','10000',5,129,'required reputation for rare quality avatar border'),('rep_req_border_epic','15000','15000',5,129,'required reputation for epic quality avatar border'),('rep_req_border_lege','25000','25000',5,129,'required reputation for legendary quality avatar border'); /*!40000 ALTER TABLE `aowow_config` ENABLE KEYS */; UNLOCK TABLES; diff --git a/setup/updates/1717513011_01.sql b/setup/updates/1717513011_01.sql new file mode 100644 index 00000000..777d40bd --- /dev/null +++ b/setup/updates/1717513011_01.sql @@ -0,0 +1 @@ +UPDATE aowow_config SET `flags` = `flags`| 0x400 WHERE `key` IN ('locales', 'acc_auth_mode', 'profiler_enable');