Files
aowow/includes/cfg.class.php
Sarjuuk db1d3ccace Core/Cleanup
* try to give included files a logical structure
 * move objects from Util and Game to their own files
 * make non-essential files auto-loaded
2025-04-01 22:33:36 +02:00

452 lines
16 KiB
PHP

<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class Cfg
{
public const PATTERN_CONF_KEY = '/[a-z0-9_\.\-]/i';
public const PATTERN_INV_CONF_KEY = '/[^a-z0-9_\.\-]/i';
public const PATTERN_INVALID_CHARS = '/\p{C}/ui';
// config flags
public const FLAG_TYPE_INT = 0x001; // validate with intVal()
public const FLAG_TYPE_FLOAT = 0x002; // validate with floatVal()
public const FLAG_TYPE_BOOL = 0x004; // 0 || 1
public const FLAG_TYPE_STRING = 0x008; //
public const FLAG_OPT_LIST = 0x010; // single option
public const FLAG_BITMASK = 0x020; // multiple options
public const FLAG_PHP = 0x040; // applied with ini_set() [restrictions apply!]
public const FLAG_PERSISTENT = 0x080; // can not be deleted
public const FLAG_REQUIRED = 0x100; // required to have non-empty value
public const FLAG_ON_LOAD_FN = 0x200; // run static function of the same name after load
public const FLAG_ON_SET_FN = 0x400; // run static function of the same name as validator
public const FLAG_INTERNAL = 0x800; // can not be configures, automaticly calculated, skip on lists
public const CAT_MISCELLANEOUS = 0;
public const CAT_SITE = 1;
public const CAT_CACHE = 2;
public const CAT_ACCOUNT = 3;
public const CAT_SESSION = 4;
public const CAT_SITE_REPUTATION = 5;
public const CAT_ANALYTICS = 6;
public const CAT_PROFILER = 7;
public static $categories = array( // don't mind the ordering ... please?
1 => 'Site', 'Caching', 'Account', 'Session', 'Site Reputation', 'Google Analytics', 'Profiler', 0 => 'Other'
);
private const IDX_VALUE = 0;
private const IDX_FLAGS = 1;
private const IDX_CATEGORY = 2;
private const IDX_DEFAULT = 3;
private const IDX_COMMENT = 4;
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
// 'rep_req_border_rare' => ['global'],
// 'rep_req_border_epic' => ['global'],
// 'rep_req_border_lege' => ['global'],
'profiler_enable' => ['realms', 'realmMenu'],
'battlegroup' => ['realms', 'realmMenu'],
'name_short' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'demo'],
'site_host' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'demo', 'power'],
'static_host' => ['searchplugin', 'searchboxBody', 'searchboxScript', 'power'],
'contact_email' => ['markup'],
'locales' => ['locales']
);
public static function load() : void
{
if (!DB::isConnectable(DB_AOWOW))
return;
$sets = DB::Aowow()->select('SELECT `key` AS ARRAY_KEY, `value` AS "0", `flags` AS "1", `cat` AS "2", `default` AS "3", `comment` AS "4" FROM ?_config ORDER BY `key` ASC');
foreach ($sets as $key => [$value, $flags, $catg, $default, $comment])
{
$php = $flags & self::FLAG_PHP;
if ($err = self::validate($value, $flags, $comment))
{
self::throwError('Aowow config '.strtoupper($key).' failed validation and was skipped: '.$err);
continue;
}
if ($flags & self::FLAG_INTERNAL)
{
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(__CLASS__, $key))
self::throwError('Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set');
else
self::{$key}($value);
}
if ($php)
ini_set(strtolower($key), $value);
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';
$key = strtolower($key);
if (preg_match(self::PATTERN_INV_CONF_KEY, $key))
return 'invalid chars in option name: [a-z 0-9 _ . -] are allowed';
if (isset(self::$store[$key]))
return 'this configuration option is already in use';
if ($errStr = self::validate($value))
return $errStr;
if (ini_get($key) === false || ini_set($key, $value) === false)
return 'this configuration option cannot be set';
$flags = self::FLAG_TYPE_STRING | self::FLAG_PHP;
if (!DB::Aowow()->query('INSERT IGNORE INTO ?_config (`key`, `value`, `cat`, `flags`) VALUES (?, ?, ?d, ?d)', $key, $value, self::CAT_MISCELLANEOUS, $flags))
return 'internal error';
self::$store[$key] = [$value, $flags, self::CAT_MISCELLANEOUS, null, null];
return '';
}
public static function delete(string $key) : string
{
if (!self::$isLoaded)
return 'used delete() on uninitialized config';
$key = strtolower($key);
if (!isset(self::$store[$key]))
return 'configuration option not found';
if (self::$store[$key][self::IDX_FLAGS] & self::FLAG_PERSISTENT)
return 'can\'t delete persistent option';
if (!(self::$store[$key][self::IDX_FLAGS] & self::FLAG_PHP))
return 'can\'t delete non-php option';
if (self::$store[$key][self::IDX_FLAGS] & self::FLAG_INTERNAL)
return 'can\'t delete internal option';
if (!DB::Aowow()->query('DELETE FROM ?_config WHERE `key` = ? AND (`flags` & ?d) = 0 AND (`flags` & ?d) > 0', $key, self::FLAG_PERSISTENT, self::FLAG_PHP))
return 'internal error';
unset(self::$store[$key]);
return '';
}
public static function get(string $key, bool $fromDB = false, bool $fullInfo = false) // : int|float|string
{
$key = strtolower($key);
if (!isset(self::$store[$key]))
{
if (self::$isLoaded)
self::throwError('cfg not defined: '.strtoupper($key));
return null;
}
if ($fromDB && $fullInfo)
return array_values(DB::Aowow()->selectRow('SELECT `value`, `flags`, `cat`, `default`, `comment` FROM ?_config WHERE `key` = ?', $key));
if ($fromDB)
return DB::Aowow()->selectCell('SELECT `value` FROM ?_config WHERE `key` = ?', $key);
if ($fullInfo)
return self::$store[$key];
return self::$store[$key][self::IDX_VALUE];
}
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]))
return 'configuration option not found';
[$oldValue, $flags, , , $comment] = self::$store[$key];
if ($flags & self::FLAG_INTERNAL)
return 'can\'t set an internal option directly';
if ($err = self::validate($value, $flags, $comment))
return $err;
if ($flags & self::FLAG_REQUIRED && !strlen($value))
return 'empty value given for required config';
DB::Aowow()->query('UPDATE ?_config SET `value` = ? WHERE `key` = ?', $value, $key);
self::$store[$key][self::IDX_VALUE] = $value;
// validate change
if ($flags & self::FLAG_ON_SET_FN)
{
$errMsg = '';
if (!method_exists(__CLASS__, $key))
$errMsg = 'Aowow config '.strtoupper($key).' flagged for onSetFN validation, but no handler was set';
else
self::{$key}($value, $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;
}
}
if ($flags & self::FLAG_ON_LOAD_FN)
{
if (!method_exists(__CLASS__, $key))
return 'Aowow config '.strtoupper($key).' flagged for onLoadFN handling, but no handler was set';
else
self::{$key}($value);
}
// trigger setup build
return self::handleFileBuild($key, $rebuildFiles);
}
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';
[$oldValue, $flags, , $default, ] = self::$store[$key];
if ($flags & self::FLAG_INTERNAL)
return 'can\'t set an internal option directly';
if (!$default)
return 'config option has no default value';
// @eval .. some dafault values are supplied as bitmask or the likes
if (!($flags & Cfg::FLAG_TYPE_STRING))
$default = @eval('return ('.$default.');');
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(__CLASS__, $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);
}
public static function forCategory(int $category) : \Generator
{
foreach (self::$store as $k => [, $flags, $catg, , ])
if ($catg == $category && !($flags & self::FLAG_INTERNAL))
yield $k => self::$store[$k];
}
public static function applyToString(string $string) : string
{
return preg_replace_callback(
['/CFG_([A-Z_]+)/', '/((HOST|STATIC)_URL)/'],
function ($m) {
if (!isset(self::$store[strtolower($m[1])]))
return $m[1];
[$val, $flags, , , ] = self::$store[strtolower($m[1])];
return $flags & (self::FLAG_TYPE_FLOAT | self::FLAG_TYPE_INT) ? Lang::nf($val) : $val;
},
$string
);
}
/************/
/* internal */
/************/
private static function validate(&$value, int $flags = self::FLAG_TYPE_STRING | self::FLAG_PHP, string $comment = ' - ') : string
{
$value = preg_replace(self::PATTERN_INVALID_CHARS, '', $value);
if (!($flags & (self::FLAG_TYPE_BOOL | self::FLAG_TYPE_FLOAT | self::FLAG_TYPE_INT | self::FLAG_TYPE_STRING)))
return 'no type set for value';
if ($flags & self::FLAG_TYPE_INT && !Util::checkNumeric($value, NUM_CAST_INT))
return 'value must be integer';
if ($flags & self::FLAG_TYPE_FLOAT && !Util::checkNumeric($value, NUM_CAST_FLOAT))
return 'value must be float';
if ($flags & self::FLAG_OPT_LIST)
{
$info = explode(' - ', $comment)[1];
foreach (explode(', ', $info) as $option)
if (explode(':', $option)[0] == $value)
return '';
return 'value not in range';
}
if ($flags & self::FLAG_BITMASK)
{
$mask = 0x0;
$info = explode(' - ', $comment)[1];
foreach (explode(', ', $info) as $option)
$mask |= (1 << explode(':', $option)[0]);
if (!($value &= $mask) && ($flags & self::FLAG_REQUIRED))
return 'value not in range';
}
if ($flags & self::FLAG_TYPE_BOOL)
$value = (bool)$value;
return '';
}
private static function handleFileBuild(string $key, array &$rebuildFiles) : string
{
if (!isset(self::$rebuildScripts[$key]))
return '';
$msg = '';
if (CLI)
{
$rebuildFiles = array_merge($rebuildFiles, self::$rebuildScripts[$key]);
return '';
}
// not in CLI mode and build() can only be run from CLI. .. todo: other options..?
exec('php aowow --build='.implode(',', self::$rebuildScripts[$key]), $out);
foreach ($out as $o)
if (strstr($o, 'ERR'))
$msg .= explode('0m]', $o)[1]."<br />\n";
return $msg;
}
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;
// note: Change is written to db and storage at this point, but can be rolled back.
if (CLISetup::setLocales())
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'))
{
$msg .= 'PHP extension GMP is required to use TrinityCore as auth source, but is not currently enabled.';
return false;
}
return true;
}
private static function profiler_enable(/*int|string*/ $value, ?string &$msg = '') : bool
{
if ($value != 1)
return true;
return Profiler::queueStart($msg);
}
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,
self::FLAG_PERSISTENT | self::FLAG_TYPE_STRING | self::FLAG_INTERNAL,
self::CAT_SITE,
null, // no default value
null, // no comment/info
);
return true;
}
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,
self::FLAG_PERSISTENT | self::FLAG_TYPE_STRING | self::FLAG_INTERNAL,
self::CAT_SITE,
null, // no default value
null, // no comment/info
);
return true;
}
private static function useSSL() : bool
{
return (($_SERVER['HTTPS'] ?? 'off') != 'off') || (self::$store['force_ssl'][self::IDX_VALUE] ?? 0);
}
}
?>