From 4f35e5e487860aeb909932bb0ec6721e562e7985 Mon Sep 17 00:00:00 2001 From: flashwave Date: Sun, 1 Jan 2023 20:23:53 +0000 Subject: [PATCH] Restructured config handler. --- lib/index | 2 +- misuzu.php | 50 ++++++------ public/auth/login.php | 11 +-- public/auth/password.php | 5 +- public/forum/leaderboard.php | 5 +- public/manage/general/setting-delete.php | 9 ++- public/manage/general/setting.php | 30 +++---- public/manage/general/settings.php | 10 ++- public/manage/news/post.php | 9 ++- public/proxy.php | 5 +- public/settings/account.php | 3 +- src/Config.php | 86 +++------------------ src/Config/CfgTools.php | 32 ++++++++ src/Config/CfgType.php | 10 +++ src/Config/DbConfig.php | 78 +++++++++++++++++++ src/Config/IConfig.php | 11 +++ src/Config/ScopedConfig.php | 58 ++++++++++++++ src/Console/Commands/TwitterAuthCommand.php | 5 +- src/Http/Handlers/ChangelogHandler.php | 5 +- src/Http/Handlers/HomeHandler.php | 13 ++-- src/Http/Handlers/NewsHandler.php | 3 +- src/MszContext.php | 11 ++- src/SharpChat/SharpChatRoutes.php | 22 +++--- src/TOTP.php | 2 +- src/Users/Assets/UserAvatarAsset.php | 5 +- src/Users/Assets/UserBackgroundAsset.php | 6 +- src/Users/Assets/UserImageAsset.php | 3 +- src/url.php | 4 +- 28 files changed, 325 insertions(+), 168 deletions(-) create mode 100644 src/Config/CfgTools.php create mode 100644 src/Config/CfgType.php create mode 100644 src/Config/DbConfig.php create mode 100644 src/Config/IConfig.php create mode 100644 src/Config/ScopedConfig.php diff --git a/lib/index b/lib/index index d166428..a0b3a08 160000 --- a/lib/index +++ b/lib/index @@ -1 +1 @@ -Subproject commit d16642872948d348f1bed805e1ce96175917aaa0 +Subproject commit a0b3a08d7f8324ea2ec4e920c38b00646e0a9a6f diff --git a/misuzu.php b/misuzu.php index c9c75ad..51ebc8b 100644 --- a/misuzu.php +++ b/misuzu.php @@ -6,6 +6,8 @@ use Index\Autoloader; use Index\Environment; use Index\Data\ConnectionFailedException; use Index\Data\DbTools; +use Misuzu\Config\CfgType; +use Misuzu\Config\DbConfig; use Misuzu\Net\GeoIP; use Misuzu\Net\IPAddress; use Misuzu\Users\User; @@ -87,19 +89,23 @@ $dbConfig = $dbConfig['Database'] ?? $dbConfig['Database.mysql-main'] ?? []; DB::init(DB::buildDSN($dbConfig), $dbConfig['username'] ?? '', $dbConfig['password'] ?? '', DB::ATTRS); DB::exec(MSZ_DB_INIT); -Config::init(); -Mailer::init(Config::get('mail.method', Config::TYPE_STR), [ - 'host' => Config::get('mail.host', Config::TYPE_STR), - 'port' => Config::get('mail.port', Config::TYPE_INT, 25), - 'username' => Config::get('mail.username', Config::TYPE_STR), - 'password' => Config::get('mail.password', Config::TYPE_STR), - 'encryption' => Config::get('mail.encryption', Config::TYPE_STR), - 'sender_name' => Config::get('mail.sender.name', Config::TYPE_STR), - 'sender_addr' => Config::get('mail.sender.address', Config::TYPE_STR), +$cfg = new DbConfig($db); +$cfg->reload(); + +Config::init($cfg); + +Mailer::init($cfg->getValue('mail.method', CfgType::T_STR), [ + 'host' => $cfg->getValue('mail.host', CfgType::T_STR), + 'port' => $cfg->getValue('mail.port', CfgType::T_INT, 25), + 'username' => $cfg->getValue('mail.username', CfgType::T_STR), + 'password' => $cfg->getValue('mail.password', CfgType::T_STR), + 'encryption' => $cfg->getValue('mail.encryption', CfgType::T_STR), + 'sender_name' => $cfg->getValue('mail.sender.name', CfgType::T_STR), + 'sender_addr' => $cfg->getValue('mail.sender.address', CfgType::T_STR), ]); // replace this with a better storage mechanism -define('MSZ_STORAGE', Config::get('storage.path', Config::TYPE_STR, MSZ_ROOT . '/store')); +define('MSZ_STORAGE', $cfg->getValue('storage.path', CfgType::T_STR, MSZ_ROOT . '/store')); if(!is_dir(MSZ_STORAGE)) mkdir(MSZ_STORAGE, 0775, true); @@ -113,7 +119,7 @@ if(MSZ_CLI) { // Temporary backwards compatibility measure, remove this later return; } -$ctx = new MszContext($db); +$ctx = new MszContext($db, $cfg); // Everything below here should eventually be moved to index.php, probably only initialised when required. // Serving things like the css/js doesn't need to initialise sessions. @@ -134,7 +140,7 @@ if(!is_readable(MSZ_STORAGE) || !is_writable(MSZ_STORAGE)) { exit; } -GeoIP::init(Config::get('geoip.database', Config::TYPE_STR, '/var/lib/GeoIP/GeoLite2-Country.mmdb')); +GeoIP::init($cfg->getValue('geoip.database', CfgType::T_STR, '/var/lib/GeoIP/GeoLite2-Country.mmdb')); if(!MSZ_DEBUG) { $twigCache = sys_get_temp_dir() . '/msz-tpl-cache-' . md5(MSZ_ROOT); @@ -145,10 +151,10 @@ if(!MSZ_DEBUG) { Template::init($ctx, $twigCache ?? null, MSZ_DEBUG); Template::set('globals', [ - 'site_name' => Config::get('site.name', Config::TYPE_STR, 'Misuzu'), - 'site_description' => Config::get('site.desc', Config::TYPE_STR), - 'site_url' => Config::get('site.url', Config::TYPE_STR), - 'site_twitter' => Config::get('social.twitter', Config::TYPE_STR), + 'site_name' => $cfg->getValue('site.name', CfgType::T_STR, 'Misuzu'), + 'site_description' => $cfg->getValue('site.desc', CfgType::T_STR), + 'site_url' => $cfg->getValue('site.url', CfgType::T_STR), + 'site_twitter' => $cfg->getValue('social.twitter', CfgType::T_STR), ]); Template::addPath(MSZ_TEMPLATES); @@ -200,21 +206,21 @@ if($authToken->isValid()) { } } -CSRF::setGlobalSecretKey(Config::get('csrf.secret', Config::TYPE_STR, 'soup')); +CSRF::setGlobalSecretKey($cfg->getValue('csrf.secret', CfgType::T_STR, 'soup')); CSRF::setGlobalIdentity(UserSession::hasCurrent() ? UserSession::getCurrent()->getToken() : IPAddress::remote()); function mszLockdown(): void { - global $misuzuBypassLockdown; + global $misuzuBypassLockdown, $cfg; - if(Config::get('private.enabled', Config::TYPE_BOOL)) { + if($cfg->getValue('private.enabled', CfgType::T_BOOL)) { $onLoginPage = $_SERVER['PHP_SELF'] === url('auth-login'); $onPasswordPage = parse_url($_SERVER['PHP_SELF'], PHP_URL_PATH) === url('auth-forgot'); $misuzuBypassLockdown = !empty($misuzuBypassLockdown) || $onLoginPage; if(!$misuzuBypassLockdown) { if(UserSession::hasCurrent()) { - $privatePermCat = Config::get('private.perm.cat', Config::TYPE_STR); - $privatePermVal = Config::get('private.perm.val', Config::TYPE_INT); + $privatePermCat = $cfg->getValue('private.perm.cat', CfgType::T_STR); + $privatePermVal = $cfg->getValue('private.perm.val', CfgType::T_INT); if(!empty($privatePermCat) && $privatePermVal > 0) { if(!perms_check_user($privatePermCat, User::getCurrent()->getId(), $privatePermVal)) { @@ -223,7 +229,7 @@ function mszLockdown(): void { User::unsetCurrent(); } } - } elseif(!$onLoginPage && !($onPasswordPage && Config::get('private.allow_password_reset', Config::TYPE_BOOL, true))) { + } elseif(!$onLoginPage && !($onPasswordPage && $cfg->getValue('private.allow_password_reset', CfgType::T_BOOL, true))) { url_redirect('auth-login'); exit; } diff --git a/public/auth/login.php b/public/auth/login.php index 493377d..a30ca70 100644 --- a/public/auth/login.php +++ b/public/auth/login.php @@ -2,6 +2,7 @@ namespace Misuzu; use Misuzu\AuthToken; +use Misuzu\Config\CfgType; use Misuzu\Net\IPAddress; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; @@ -41,9 +42,9 @@ if(!empty($_GET['resolve'])) { } $notices = []; -$siteIsPrivate = Config::get('private.enable', Config::TYPE_BOOL); -$loginPermCat = $siteIsPrivate ? Config::get('private.perm.cat', Config::TYPE_STR) : ''; -$loginPermVal = $siteIsPrivate ? Config::get('private.perm.val', Config::TYPE_INT) : 0; +$siteIsPrivate = $cfg->getValue('private.enable', CfgType::T_BOOL); +$loginPermCat = $siteIsPrivate ? $cfg->getValue('private.perm.cat', CfgType::T_STR) : ''; +$loginPermVal = $siteIsPrivate ? $cfg->getValue('private.perm.val', CfgType::T_INT) : 0; $remainingAttempts = UserLoginAttempt::remaining(); while(!empty($_POST['login']) && is_array($_POST['login'])) { @@ -132,8 +133,8 @@ $loginUsername = !empty($_POST['login']['username']) && is_string($_POST['login' !empty($_GET['username']) && is_string($_GET['username']) ? $_GET['username'] : '' ); $loginRedirect = $welcomeMode ? url('index') : (!empty($_GET['redirect']) && is_string($_GET['redirect']) ? $_GET['redirect'] : null) ?? $_SERVER['HTTP_REFERER'] ?? url('index'); -$sitePrivateMessage = $siteIsPrivate ? Config::get('private.msg', Config::TYPE_STR) : ''; -$canResetPassword = $siteIsPrivate ? Config::get('private.allow_password_reset', Config::TYPE_BOOL, true) : true; +$sitePrivateMessage = $siteIsPrivate ? $cfg->getValue('private.msg', CfgType::T_STR) : ''; +$canResetPassword = $siteIsPrivate ? $cfg->getValue('private.allow_password_reset', CfgType::T_BOOL, true) : true; $canRegisterAccount = !$siteIsPrivate; Template::render('auth.login', [ diff --git a/public/auth/password.php b/public/auth/password.php index 7cc0e0b..e4d3043 100644 --- a/public/auth/password.php +++ b/public/auth/password.php @@ -2,6 +2,7 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Config\CfgType; use Misuzu\Users\User; use Misuzu\Users\UserNotFoundException; use Misuzu\Users\UserLoginAttempt; @@ -32,8 +33,8 @@ if($userId > 0) } $notices = []; -$siteIsPrivate = Config::get('private.enable', Config::TYPE_BOOL); -$canResetPassword = $siteIsPrivate ? Config::get('private.allow_password_reset', Config::TYPE_BOOL, true) : true; +$siteIsPrivate = $cfg->getValue('private.enable', CfgType::T_BOOL); +$canResetPassword = $siteIsPrivate ? $cfg->getValue('private.allow_password_reset', CfgType::T_BOOL, true) : true; $remainingAttempts = UserLoginAttempt::remaining(); while($canResetPassword) { diff --git a/public/forum/leaderboard.php b/public/forum/leaderboard.php index 97eda9b..98dded7 100644 --- a/public/forum/leaderboard.php +++ b/public/forum/leaderboard.php @@ -1,6 +1,7 @@ getValue('forum_leader.unranked.forum', CfgType::T_ARR); +$unrankedTopics = !empty($_GET['allow_unranked']) ? [] : $cfg->getValue('forum_leader.unranked.topic', CfgType::T_ARR); $leaderboards = forum_leaderboard_categories(); $leaderboard = forum_leaderboard_listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics); diff --git a/public/manage/general/setting-delete.php b/public/manage/general/setting-delete.php index 21c98bb..0db5a70 100644 --- a/public/manage/general/setting-delete.php +++ b/public/manage/general/setting-delete.php @@ -3,6 +3,7 @@ namespace Misuzu; use Misuzu\AuditLog; use Misuzu\Config; +use Misuzu\Config\CfgTools; use Misuzu\Users\User; require_once '../../../misuzu.php'; @@ -14,7 +15,7 @@ if(!User::hasCurrent() } $sName = (string)filter_input(INPUT_GET, 'name'); -if(!Config::validateName($sName) || !Config::has($sName)) +if(!CfgTools::validateName($sName) || !$cfg->hasValue($sName)) throw new \Exception("Config value does not exist."); if($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -22,15 +23,15 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { throw new \Exception("Request verification failed."); AuditLog::create(AuditLog::CONFIG_DELETE, [$sName]); - Config::remove($sName); + $cfg->removeValue($sName); url_redirect('manage-general-settings'); } else { - $sValue = Config::get($sName); + $sValue = $cfg->getValue($sName); Template::render('manage.general.setting-delete', [ 'conf_var' => [ 'name' => $sName, - 'type' => Config::type($sValue), + 'type' => CfgTools::type($sValue), 'value' => $sValue, ], ]); diff --git a/public/manage/general/setting.php b/public/manage/general/setting.php index 1ba28af..44a4f75 100644 --- a/public/manage/general/setting.php +++ b/public/manage/general/setting.php @@ -2,6 +2,8 @@ namespace Misuzu; use Misuzu\Config; +use Misuzu\Config\CfgTools; +use Misuzu\Config\CfgType; use Misuzu\Users\User; require_once '../../../misuzu.php'; @@ -22,7 +24,7 @@ $sVar = [ $sName = (string)filter_input(INPUT_GET, 'name'); if(!empty($sName)) { - if(!Config::validateName($sName)) + if(!CfgTools::validateName($sName)) throw new \Exception("Config key name has invalid format."); $sVar['name'] = $sName; @@ -30,11 +32,11 @@ if(!empty($sName)) { $sType = (string)filter_input(INPUT_GET, 'type'); if(!empty($sType)) { - if(!Config::isValidType($sType)) + if(!CfgTools::isValidType($sType)) throw new \Exception("Specified type is invalid."); $sVar['type'] = $sType; - $sVar['value'] = Config::default($sType); + $sVar['value'] = CfgTools::default($sType); } if($_SERVER['REQUEST_METHOD'] === 'POST') { @@ -44,7 +46,7 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { if(empty($sName)) { $sName = (string)filter_input(INPUT_POST, 'conf_name'); - if(empty($sName) || !Config::validateName($sName)) + if(empty($sName) || !CfgTools::validateName($sName)) throw new \Exception("Config key name has invalid format."); $sVar['name'] = $sName; @@ -52,24 +54,24 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { $sLogAction = AuditLog::CONFIG_CREATE; - if(Config::has($sName)) { - $sType = Config::type(Config::get($sName)); + if($cfg->hasValue($sName)) { + $sType = CfgTools::type($cfg->getValue($sName)); $sVar['new'] = false; $sLogAction = AuditLog::CONFIG_UPDATE; } elseif(empty($sType)) { $sType = (string)filter_input(INPUT_POST, 'conf_type'); - if(empty($sType) || !Config::isValidType($sType)) + if(empty($sType) || !CfgTools::isValidType($sType)) throw new \Exception("Specified type is invalid."); } $sVar['type'] = $sType; - $sValue = Config::default($sType); + $sValue = CfgTools::default($sType); if($sType === 'array') { if(!empty($_POST['conf_value']) && is_array($_POST['conf_value'])) { foreach($_POST['conf_value'] as $fv) { - $fv = strval($fv); + $fv = (string)$fv; if(str_starts_with($fv, 's:')) { $fv = substr($fv, 2); @@ -94,17 +96,17 @@ if($_SERVER['REQUEST_METHOD'] === 'POST') { $sVar['value'] = $sValue; AuditLog::create($sLogAction, [$sName]); - Config::set($sName, $sValue); + $cfg->setValue($sName, $sValue); url_redirect('manage-general-settings'); return; } -if(Config::has($sName)) { +if($cfg->hasValue($sName)) { $sVar['new'] = false; - $sValue = Config::get($sName); - $sVar['type'] = $sType = Config::type($sValue); + $sValue = $cfg->getValue($sName); + $sVar['type'] = $sType = CfgTools::type($sValue); - if($sType === Config::TYPE_ARR) + if($sType === CfgType::T_ARR) foreach($sValue as $fk => $fv) $sValue[$fk] = ['integer' => 'i', 'string' => 's', 'boolean' => 'b'][gettype($fv)] . ':' . $fv; diff --git a/public/manage/general/settings.php b/public/manage/general/settings.php index 6600b2c..87966e2 100644 --- a/public/manage/general/settings.php +++ b/public/manage/general/settings.php @@ -2,6 +2,8 @@ namespace Misuzu; use Misuzu\Config; +use Misuzu\Config\CfgTools; +use Misuzu\Config\CfgType; use Misuzu\Users\User; require_once '../../../misuzu.php'; @@ -12,14 +14,14 @@ if(!User::hasCurrent() return; } -$hidden = Config::get('settings.hidden', Config::TYPE_ARR, []); +$hidden = $cfg->getValue('settings.hidden', CfgType::T_ARR, []); $vars = []; -foreach(Config::keys() as $key) { - $var = Config::get($key); +foreach($cfg->getNames() as $key) { + $var = $cfg->getValue($key); $vars[] = [ 'key' => $key, - 'type' => Config::type($var), + 'type' => CfgTools::type($var), 'value' => in_array($key, $hidden) ? '*** hidden ***' : json_encode($var), ]; } diff --git a/public/manage/news/post.php b/public/manage/news/post.php index ccdb0b7..64c46d3 100644 --- a/public/manage/news/post.php +++ b/public/manage/news/post.php @@ -2,6 +2,7 @@ namespace Misuzu; use Misuzu\AuditLog; +use Misuzu\Config\CfgType; use Misuzu\News\NewsCategory; use Misuzu\News\NewsPost; use Misuzu\News\NewsPostNotFoundException; @@ -52,10 +53,10 @@ if(!empty($_POST['post']) && CSRF::validateRequest()) { if(!empty($isNew)) { if($postInfo->isFeatured()) { - $twitterApiKey = Config::get('twitter.api.key', Config::TYPE_STR); - $twitterApiSecret = Config::get('twitter.api.secret', Config::TYPE_STR); - $twitterToken = Config::get('twitter.token.key', Config::TYPE_STR); - $twitterTokenSecret = Config::get('twitter.token.secret', Config::TYPE_STR); + $twitterApiKey = $cfg->getValue('twitter.api.key', CfgType::T_STR); + $twitterApiSecret = $cfg->getValue('twitter.api.secret', CfgType::T_STR); + $twitterToken = $cfg->getValue('twitter.token.key', CfgType::T_STR); + $twitterTokenSecret = $cfg->getValue('twitter.token.secret', CfgType::T_STR); if(!empty($twitterApiKey) && !empty($twitterApiSecret) && !empty($twitterToken) && !empty($twitterTokenSecret)) { diff --git a/public/proxy.php b/public/proxy.php index 84ee915..0594aff 100644 --- a/public/proxy.php +++ b/public/proxy.php @@ -2,6 +2,7 @@ namespace Misuzu; use Index\Serialisation\Serialiser; +use Misuzu\Config\CfgType; require_once '../misuzu.php'; @@ -37,12 +38,12 @@ if(empty($parsedUrl['scheme']) return; } -if(!Config::get('media_proxy.enable', Config::TYPE_BOOL)) { +if(!$cfg->getValue('media_proxy.enable', CfgType::T_BOOL)) { redirect($proxyUrlDecoded); return; } -$proxySecret = Config::get('media_proxy.secret', Config::TYPE_STR, 'insecure'); +$proxySecret = $cfg->getValue('media_proxy.secret', CfgType::T_STR, 'insecure'); $expectedHash = hash_hmac('sha256', $proxyUrl, $proxySecret); if(!hash_equals($expectedHash, $proxyHash)) { diff --git a/public/settings/account.php b/public/settings/account.php index 3932ed4..953ff87 100644 --- a/public/settings/account.php +++ b/public/settings/account.php @@ -3,6 +3,7 @@ namespace Misuzu; use Misuzu\AuditLog; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\Users\User; use Misuzu\Users\UserRole; use Misuzu\Users\UserRoleNotFoundException; @@ -49,7 +50,7 @@ if(!$isRestricted && $isVerifiedRequest && !empty($_POST['role'])) { if($isVerifiedRequest && isset($_POST['tfa']['enable']) && $currentUser->hasTOTP() !== (bool)$_POST['tfa']['enable']) { if((bool)$_POST['tfa']['enable']) { $tfaKey = TOTP::generateKey(); - $tfaIssuer = Config::get('site.name', Config::TYPE_STR, 'Misuzu'); + $tfaIssuer = $cfg->getValue('site.name', CfgType::T_STR, 'Misuzu'); $tfaQrcode = (new QRCode(new QROptions([ 'version' => 5, 'outputType' => QRCode::OUTPUT_IMAGE_JPG, diff --git a/src/Config.php b/src/Config.php index 4664ae9..43ad961 100644 --- a/src/Config.php +++ b/src/Config.php @@ -1,97 +1,33 @@ null, - self::TYPE_STR => '', - self::TYPE_INT => 0, - self::TYPE_BOOL => false, - self::TYPE_ARR => [], - ]; - - private static $config = []; - - public static function init(): void { - try { - $config = DB::prepare('SELECT * FROM `msz_config`')->fetchAll(); - } catch(PDOException $ex) { - return; - } - - - foreach($config as $record) { - self::$config[$record['config_name']] = unserialize($record['config_value']); - } + public static function init(IConfig $config): void { + self::$config = $config; } public static function keys(): array { - return array_keys(self::$config); + return self::$config->getNames(); } - public static function type($value): string { - return gettype($value); - } - - public static function isValidType(string $type): bool { - return $type === self::TYPE_ARR - || $type === self::TYPE_BOOL - || $type === self::TYPE_INT - || $type === self::TYPE_STR; - } - - public static function validateName(string $name): bool { - // this should better validate the format, this allows for a lot of shittery - return preg_match('#^([a-z][a-zA-Z0-9._]+)$#', $name) === 1; - } - - public static function default(string $type) { - return self::DEFAULTS[$type] ?? null; - } - - public static function get(string $key, string $type = self::TYPE_ANY, $default = null) { - $value = self::$config[$key] ?? null; - - if($type !== self::TYPE_ANY && gettype($value) !== $type) - $value = null; - - return $value ?? $default ?? self::DEFAULTS[$type]; + public static function get(string $key, string $type = CfgType::T_ANY, $default = null): mixed { + return self::$config->getValue($key, $type, $default); } public static function has(string $key): bool { - return array_key_exists($key, self::$config); + return self::$config->hasValue($key); } public static function set(string $key, $value, bool $soft = false): void { - self::$config[$key] = $value; - - if(!$soft) { - $value = serialize($value); - - DB::prepare(' - REPLACE INTO `msz_config` - (`config_name`, `config_value`) - VALUES - (:name, :value) - ')->bind('name', $key) - ->bind('value', $value) - ->execute(); - } + self::$config->setValue($key, $value, !$soft); } public static function remove(string $key, bool $soft = false): void { - unset(self::$config[$key]); - - if(!$soft) - DB::prepare('DELETE FROM `msz_config` WHERE `config_name` = :name')->bind('name', $key)->execute(); + self::$config->removeValue($key, !$soft); } } diff --git a/src/Config/CfgTools.php b/src/Config/CfgTools.php new file mode 100644 index 0000000..2562e7b --- /dev/null +++ b/src/Config/CfgTools.php @@ -0,0 +1,32 @@ + null, + CfgType::T_STR => '', + CfgType::T_INT => 0, + CfgType::T_BOOL => false, + CfgType::T_ARR => [], + ]; + + public static function default(string $type) { + return self::DEFAULTS[$type] ?? null; + } +} diff --git a/src/Config/CfgType.php b/src/Config/CfgType.php new file mode 100644 index 0000000..e6407b5 --- /dev/null +++ b/src/Config/CfgType.php @@ -0,0 +1,10 @@ +dbConn = $dbConn; + } + + public function reload(): void { + $this->values = []; + + try { + $result = $this->dbConn->query('SELECT config_name, config_value FROM msz_config;'); + while($result->next()) + $this->values[$result->getString(0)] = unserialize($result->getString(1)); + } catch(DataException $ex) { } + } + + public function scopeTo(string $prefix): IConfig { + return new ScopedConfig($this, $prefix); + } + + public function getNames(): array { + return array_keys($this->values); + } + + public function getValue(string $name, string $type = CfgType::T_ANY, $default = null): mixed { + $value = $this->values[$name] ?? null; + + if($type !== CfgType::T_ANY && CfgTools::type($value) !== $type) + $value = null; + + return $value ?? $default ?? CfgTools::default($type); + } + + public function hasValue(string $name): bool { + return array_key_exists($name, $this->values); + } + + public function setValue(string $name, $value, bool $save = true): void { + $this->values[$name] = $value; + + if($save) { + $value = serialize($value); + + if($this->stmtSet === null) + $this->stmtSet = $this->dbConn->prepare('REPLACE INTO msz_config (config_name, config_value) VALUES (?, ?)'); + + $this->stmtSet->reset(); + $this->stmtSet->addParameter(1, $name); + $this->stmtSet->addParameter(2, $value); + $this->stmtSet->execute(); + } + } + + public function removeValue(string $name, bool $save = true): void { + unset($this->values[$name]); + + if($save) { + if($this->stmtDelete === null) + $this->stmtDelete = $this->dbConn->prepare('DELETE FROM msz_config WHERE config_name = ?'); + + $this->stmtDelete->reset(); + $this->stmtDelete->addParameter(1, $name); + $this->stmtDelete->execute(); + } + } +} diff --git a/src/Config/IConfig.php b/src/Config/IConfig.php new file mode 100644 index 0000000..8cbaf4b --- /dev/null +++ b/src/Config/IConfig.php @@ -0,0 +1,11 @@ +config = $config; + $this->prefix = $prefix; + } + + private function getName(string $name): string { + return $this->prefix . $name; + } + + public function scopeTo(string $prefix): IConfig { + return $this->config->scopeTo($this->getName($prefix)); + } + + public function getNames(): array { + $orig = $this->config->getNames(); + $pfxLength = strlen($this->prefix); + $filter = []; + + foreach($orig as $name) + if(str_starts_with($name, $this->prefix)) + $filter[] = substr($name, $pfxLength); + + return $filter; + } + + public function getValue(string $name, string $type = CfgType::T_ANY, $default = null): mixed { + return $this->config->getValue($this->getName($name), $type, $default); + } + + public function hasValue(string $name): bool { + return $this->config->hasValue($this->getName($name)); + } + + public function setValue(string $name, $value, bool $save = true): void { + $this->config->setValue($this->getName($name), $value, $save); + } + + public function removeValue(string $name, bool $save = true): void { + $this->config->removeValue($this->getName($name), $save); + } +} diff --git a/src/Console/Commands/TwitterAuthCommand.php b/src/Console/Commands/TwitterAuthCommand.php index 421c2c1..660d6a9 100644 --- a/src/Console/Commands/TwitterAuthCommand.php +++ b/src/Console/Commands/TwitterAuthCommand.php @@ -2,6 +2,7 @@ namespace Misuzu\Console\Commands; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\Twitter; use Misuzu\Console\CommandArgs; use Misuzu\Console\CommandInterface; @@ -15,8 +16,8 @@ class TwitterAuthCommand implements CommandInterface { } public function dispatch(CommandArgs $args): void { - $apiKey = Config::get('twitter.api.key', Config::TYPE_STR); - $apiSecret = Config::get('twitter.api.secret', Config::TYPE_STR); + $apiKey = Config::get('twitter.api.key', CfgType::T_STR); + $apiSecret = Config::get('twitter.api.secret', CfgType::T_STR); if(empty($apiKey) || empty($apiSecret)) { echo 'No Twitter api keys set in config.' . PHP_EOL; diff --git a/src/Http/Handlers/ChangelogHandler.php b/src/Http/Handlers/ChangelogHandler.php index 729e3b7..bdbc70b 100644 --- a/src/Http/Handlers/ChangelogHandler.php +++ b/src/Http/Handlers/ChangelogHandler.php @@ -3,6 +3,7 @@ namespace Misuzu\Http\Handlers; use ErrorException; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\Pagination; use Misuzu\Template; use Misuzu\Changelog\ChangelogChange; @@ -83,8 +84,8 @@ class ChangelogHandler extends Handler { $changes = ChangelogChange::all(new Pagination(10)); $feed = (new Feed) - ->setTitle(Config::get('site.name', Config::TYPE_STR, 'Misuzu') . ' » Changelog') - ->setDescription('Live feed of changes to ' . Config::get('site.name', Config::TYPE_STR, 'Misuzu') . '.') + ->setTitle(Config::get('site.name', CfgType::T_STR, 'Misuzu') . ' » Changelog') + ->setDescription('Live feed of changes to ' . Config::get('site.name', CfgType::T_STR, 'Misuzu') . '.') ->setContentUrl(url_prefix(false) . url('changelog-index')) ->setFeedUrl(url_prefix(false) . url("changelog-feed-{$feedMode}")); diff --git a/src/Http/Handlers/HomeHandler.php b/src/Http/Handlers/HomeHandler.php index 5865043..d322ab8 100644 --- a/src/Http/Handlers/HomeHandler.php +++ b/src/Http/Handlers/HomeHandler.php @@ -2,6 +2,7 @@ namespace Misuzu\Http\Handlers; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\DB; use Misuzu\Pagination; use Misuzu\Template; @@ -19,12 +20,12 @@ final class HomeHandler extends Handler { } public function landing($response, $request): void { - $linkedData = Config::get('social.embed_linked', Config::TYPE_BOOL) + $linkedData = Config::get('social.embed_linked', CfgType::T_BOOL) ? [ - 'name' => Config::get('site.name', Config::TYPE_STR, 'Misuzu'), - 'url' => Config::get('site.url', Config::TYPE_STR), - 'logo' => Config::get('site.ext_logo', Config::TYPE_STR), - 'same_as' => Config::get('social.linked', Config::TYPE_ARR), + 'name' => Config::get('site.name', CfgType::T_STR, 'Misuzu'), + 'url' => Config::get('site.url', CfgType::T_STR), + 'logo' => Config::get('site.ext_logo', CfgType::T_STR), + 'same_as' => Config::get('social.linked', CfgType::T_ARR), ] : null; @@ -51,7 +52,7 @@ final class HomeHandler extends Handler { )->fetchAll(); // TODO: don't hardcode forum ids - $featuredForums = Config::get('landing.forum_categories', Config::TYPE_ARR); + $featuredForums = Config::get('landing.forum_categories', CfgType::T_ARR); $popularTopics = []; $activeTopics = []; diff --git a/src/Http/Handlers/NewsHandler.php b/src/Http/Handlers/NewsHandler.php index f461287..f48a1d9 100644 --- a/src/Http/Handlers/NewsHandler.php +++ b/src/Http/Handlers/NewsHandler.php @@ -2,6 +2,7 @@ namespace Misuzu\Http\Handlers; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\DB; use Misuzu\Pagination; use Misuzu\Template; @@ -85,7 +86,7 @@ final class NewsHandler extends Handler { $posts = $hasCategory ? $categoryInfo->posts($pagination) : NewsPost::all($pagination, true); $feed = (new Feed) - ->setTitle(Config::get('site.name', Config::TYPE_STR, 'Misuzu') . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News')) + ->setTitle(Config::get('site.name', CfgType::T_STR, 'Misuzu') . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News')) ->setDescription($hasCategory ? $categoryInfo->getDescription() : 'A live featured news feed.') ->setContentUrl(url_prefix(false) . ($hasCategory ? url('news-category', ['category' => $categoryInfo->getId()]) : url('news-index'))) ->setFeedUrl(url_prefix(false) . ($hasCategory ? url("news-category-feed-{$feedMode}", ['category' => $categoryInfo->getId()]) : url("news-feed-{$feedMode}"))); diff --git a/src/MszContext.php b/src/MszContext.php index d617419..85e6ad8 100644 --- a/src/MszContext.php +++ b/src/MszContext.php @@ -1,6 +1,7 @@ dbConn = $dbConn; + $this->config = $config; $this->users = new Users($this->dbConn); } @@ -27,6 +30,10 @@ class MszContext { return $result->next() ? $result->getInteger(0) : 0; } + public function getConfig(): IConfig { + return $this->config; + } + public function getRouter(): Router { return $this->router->getRouter(); } @@ -103,7 +110,7 @@ class MszContext { $this->router->get('/forum/mark-as-read', msz_compat_handler('Forum', 'markAsReadGET')); $this->router->post('/forum/mark-as-read', msz_compat_handler('Forum', 'markAsReadPOST')); - new SharpChatRoutes($this); + new SharpChatRoutes($this->router, $this->config->scopeTo('sockChat')); } private function registerLegacyRedirects(): void { diff --git a/src/SharpChat/SharpChatRoutes.php b/src/SharpChat/SharpChatRoutes.php index e10850b..7fe8413 100644 --- a/src/SharpChat/SharpChatRoutes.php +++ b/src/SharpChat/SharpChatRoutes.php @@ -1,11 +1,12 @@ config = $config; + + $hashKey = $this->config->getValue('hashKey', CfgType::T_STR, ''); if(empty($hashKey)) { - $hashKeyPath = Config::get('sockChat.hashKeyPath', Config::TYPE_STR, ''); + $hashKeyPath = $this->config->getValue('hashKeyPath', CfgType::T_STR, ''); if(is_file($hashKeyPath)) $this->hashKey = file_get_contents($hashKeyPath); } else { $this->hashKey = $hashKey; } - $router = $ctx->getRouter(); - // Public endpoints $router->get('/_sockchat/emotes', [$this, 'emotes']); $router->get('/_sockchat/login', [$this, 'login']); @@ -70,8 +72,8 @@ final class SharpChatRoutes { public static function login($response, $request): void { $currentUser = User::getCurrent(); - $configKey = $request->hasParam('legacy') ? 'sockChat.chatPath.legacy' : 'sockChat.chatPath.normal'; - $chatPath = Config::get($configKey, Config::TYPE_STR, '/'); + $configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal'; + $chatPath = $this->config->getValue($configKey, CfgType::T_STR, '/'); $response->redirect( $currentUser === null @@ -86,7 +88,7 @@ final class SharpChatRoutes { $originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? ''); if(!empty($originHost) && $originHost !== $host) { - $whitelist = Config::get('sockChat.origins', Config::TYPE_ARR, []); + $whitelist = $this->config->getValue('origins', CfgType::T_ARR, []); if(!in_array($originHost, $whitelist)) return 403; diff --git a/src/TOTP.php b/src/TOTP.php index 23971f9..acbc5dc 100644 --- a/src/TOTP.php +++ b/src/TOTP.php @@ -34,6 +34,6 @@ class TOTP { $bin |= (ord($hash[$offset + 3]) & 0xFF); $otp = $bin % pow(10, self::DIGITS); - return str_pad(strval($otp), self::DIGITS, '0', STR_PAD_LEFT); + return str_pad((string)$otp, self::DIGITS, '0', STR_PAD_LEFT); } } diff --git a/src/Users/Assets/UserAvatarAsset.php b/src/Users/Assets/UserAvatarAsset.php index bfafba2..683c610 100644 --- a/src/Users/Assets/UserAvatarAsset.php +++ b/src/Users/Assets/UserAvatarAsset.php @@ -2,6 +2,7 @@ namespace Misuzu\Users\Assets; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\Imaging\Image; use Misuzu\Users\User; @@ -19,13 +20,13 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa private const MAX_BYTES = 1048576; public function getMaxWidth(): int { - return Config::get('avatar.max_res', Config::TYPE_INT, self::MAX_RES); + return Config::get('avatar.max_res', CfgType::T_INT, self::MAX_RES); } public function getMaxHeight(): int { return $this->getMaxWidth(); } public function getMaxBytes(): int { - return Config::get('avatar.max_size', Config::TYPE_INT, self::MAX_BYTES); + return Config::get('avatar.max_size', CfgType::T_INT, self::MAX_BYTES); } public function getUrl(): string { diff --git a/src/Users/Assets/UserBackgroundAsset.php b/src/Users/Assets/UserBackgroundAsset.php index 991ec86..3af42c6 100644 --- a/src/Users/Assets/UserBackgroundAsset.php +++ b/src/Users/Assets/UserBackgroundAsset.php @@ -48,13 +48,13 @@ class UserBackgroundAsset extends UserImageAsset { } public function getMaxWidth(): int { - return Config::get('background.max_width', Config::TYPE_INT, self::MAX_WIDTH); + return Config::get('background.max_width', CfgType::T_INT, self::MAX_WIDTH); } public function getMaxHeight(): int { - return Config::get('background.max_height', Config::TYPE_INT, self::MAX_HEIGHT); + return Config::get('background.max_height', CfgType::T_INT, self::MAX_HEIGHT); } public function getMaxBytes(): int { - return Config::get('background.max_size', Config::TYPE_INT, self::MAX_BYTES); + return Config::get('background.max_size', CfgType::T_INT, self::MAX_BYTES); } public function getUrl(): string { diff --git a/src/Users/Assets/UserImageAsset.php b/src/Users/Assets/UserImageAsset.php index 8912600..ef5eed5 100644 --- a/src/Users/Assets/UserImageAsset.php +++ b/src/Users/Assets/UserImageAsset.php @@ -3,6 +3,7 @@ namespace Misuzu\Users\Assets; use JsonSerializable; use Misuzu\Config; +use Misuzu\Config\CfgType; use Misuzu\Users\User; class UserImageAssetFileCreationFailedException extends UserAssetException {} @@ -83,7 +84,7 @@ abstract class UserImageAsset implements JsonSerializable, UserImageAssetInterfa } public function getStoragePath(): string { - return Config::get('storage.path', Config::TYPE_STR, MSZ_ROOT . DIRECTORY_SEPARATOR . 'store'); + return Config::get('storage.path', CfgType::T_STR, MSZ_ROOT . DIRECTORY_SEPARATOR . 'store'); } public function getPath(): string { diff --git a/src/url.php b/src/url.php index 17adf11..6e39314 100644 --- a/src/url.php +++ b/src/url.php @@ -249,11 +249,11 @@ function url_construct(string $url, array $query = [], ?string $fragment = null) } function url_proxy_media(?string $url): ?string { - if(empty($url) || !\Misuzu\Config::get('media_proxy.enable', \Misuzu\Config::TYPE_BOOL) || is_local_url($url)) { + if(empty($url) || !\Misuzu\Config::get('media_proxy.enable', \Misuzu\Config\CfgType::T_BOOL) || is_local_url($url)) { return $url; } - $secret = \Misuzu\Config::get('media_proxy.secret', \Misuzu\Config::TYPE_STR, 'insecure'); + $secret = \Misuzu\Config::get('media_proxy.secret', \Misuzu\Config\CfgType::T_STR, 'insecure'); $url = \Index\Serialisation\Serialiser::uriBase64()->serialise($url); $hash = hash_hmac('sha256', $url, $secret);