Config class overhaul.

This commit is contained in:
flash 2023-07-18 21:48:44 +00:00
parent cecfaf4852
commit 2f7cddde19
30 changed files with 838 additions and 396 deletions

View file

@ -36,10 +36,13 @@
.manage-list-setting-type--string {
--type-colour: #ef8323;
}
.manage-list-setting-type--integer {
.manage-list-setting-type--int {
--type-colour: #8c90bc;
}
.manage-list-setting-type--boolean {
.manage-list-setting-type--float {
--type-colour: #cb09c8;
}
.manage-list-setting-type--bool {
--type-colour: #77b34c;
}
.manage-list-setting-type--array {

View file

@ -5,7 +5,6 @@ use Index\Autoloader;
use Index\Environment;
use Index\Data\ConnectionFailedException;
use Index\Data\DbTools;
use Misuzu\Config\IConfig;
use Misuzu\Config\DbConfig;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
@ -47,7 +46,7 @@ set_exception_handler(function(\Throwable $ex) {
header('Content-Type: text/plain; charset=utf-8');
echo (string)$ex;
} else {
header('Content-Type: text/html; charset-utf-8');
header('Content-Type: text/html; charset=utf-8');
echo file_get_contents(MSZ_TEMPLATES . '/500.html');
}
}
@ -83,24 +82,8 @@ DB::init(DbTools::parse($dbConfig['dsn']));
DB::exec(MSZ_DB_INIT);
$cfg = new DbConfig($db);
$cfg->reload();
Config::init($cfg);
Mailer::init($cfg->getValue('mail.method', IConfig::T_STR), [
'host' => $cfg->getValue('mail.host', IConfig::T_STR),
'port' => $cfg->getValue('mail.port', IConfig::T_INT, 25),
'username' => $cfg->getValue('mail.username', IConfig::T_STR),
'password' => $cfg->getValue('mail.password', IConfig::T_STR),
'encryption' => $cfg->getValue('mail.encryption', IConfig::T_STR),
'sender_name' => $cfg->getValue('mail.sender.name', IConfig::T_STR),
'sender_addr' => $cfg->getValue('mail.sender.address', IConfig::T_STR),
]);
// replace this with a better storage mechanism
define('MSZ_STORAGE', $cfg->getValue('storage.path', IConfig::T_STR, MSZ_ROOT . '/store'));
if(!is_dir(MSZ_STORAGE))
mkdir(MSZ_STORAGE, 0775, true);
Mailer::init($cfg->scopeTo('mail'));
$msz = new MisuzuContext($db, $cfg);
@ -115,17 +98,12 @@ ob_start();
if(file_exists(MSZ_ROOT . '/.migrating')) {
http_response_code(503);
if(!isset($_GET['_check'])) {
header('Content-Type: text/html; charset-utf-8');
header('Content-Type: text/html; charset=utf-8');
echo file_get_contents(MSZ_TEMPLATES . '/503.html');
}
exit;
}
if(!is_readable(MSZ_STORAGE) || !is_writable(MSZ_STORAGE)) {
echo 'Cannot access storage directory.';
exit;
}
if(!MSZ_DEBUG) {
$twigCacheDirSfx = GitInfo::hash(true);
if(empty($twigCacheDirSfx))
@ -136,16 +114,27 @@ if(!MSZ_DEBUG) {
mkdir($twigCache, 0775, true);
}
$globals = $cfg->getValues([
['site.name:s', 'Misuzu'],
'site.desc:s',
'site.url:s',
'sockChat.chatPath.normal:s',
'eeprom.path:s',
'eeprom.app:s',
['auth.secret:s', 'meow'],
['csrf.secret:s', 'soup'],
]);
Template::init($msz, $twigCache ?? null, MSZ_DEBUG);
Template::set('globals', [
'site_name' => $cfg->getValue('site.name', IConfig::T_STR, 'Misuzu'),
'site_description' => $cfg->getValue('site.desc', IConfig::T_STR),
'site_url' => $cfg->getValue('site.url', IConfig::T_STR),
'site_chat' => $cfg->getValue('sockChat.chatPath.normal', IConfig::T_STR),
'site_name' => $globals['site.name'],
'site_description' => $globals['site.desc'],
'site_url' => $globals['site.url'],
'site_chat' => $globals['sockChat.chatPath.normal'],
'eeprom' => [
'path' => $cfg->getValue('eeprom.path', IConfig::T_STR),
'app' => $cfg->getValue('eeprom.app', IConfig::T_STR),
'path' => $globals['eeprom.path'],
'app' => $globals['eeprom.app'],
],
]);
@ -156,7 +145,7 @@ unset($mszAssetsInfo);
Template::addPath(MSZ_TEMPLATES);
AuthToken::setSecretKey($cfg->getValue('auth.secret', IConfig::T_STR, 'meow'));
AuthToken::setSecretKey($globals['auth.secret']);
if(isset($_COOKIE['msz_uid']) && isset($_COOKIE['msz_sid'])) {
$authToken = new AuthToken;
@ -220,22 +209,21 @@ if($authToken->isValid()) {
}
CSRF::init(
$cfg->getValue('csrf.secret', IConfig::T_STR, 'soup'),
$globals['csrf.secret'],
(UserSession::hasCurrent() ? UserSession::getCurrent()->getToken() : ($_SERVER['REMOTE_ADDR'] ?? '::1'))
);
function mszLockdown(): void {
global $misuzuBypassLockdown, $cfg;
if($cfg->getValue('private.enabled', IConfig::T_BOOL)) {
if($cfg->getBoolean('private.enabled')) {
$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 = $cfg->getValue('private.perm.cat', IConfig::T_STR);
$privatePermVal = $cfg->getValue('private.perm.val', IConfig::T_INT);
['private.perm.cat' => $privatePermCat, 'private.perm.val' => $privatePermVal] = $cfg->getValues(['private.perm.cat:s', 'private.perm.val:i']);
if(!empty($privatePermCat) && $privatePermVal > 0) {
if(!perms_check_user($privatePermCat, User::getCurrent()->getId(), $privatePermVal)) {
@ -244,7 +232,7 @@ function mszLockdown(): void {
User::unsetCurrent();
}
}
} elseif(!$onLoginPage && !($onPasswordPage && $cfg->getValue('private.allow_password_reset', IConfig::T_BOOL, true))) {
} elseif(!$onLoginPage && !($onPasswordPage && $cfg->getBoolean('private.allow_password_reset', true))) {
url_redirect('auth-login');
exit;
}

View file

@ -2,7 +2,6 @@
namespace Misuzu;
use Misuzu\AuthToken;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserAuthSession;
@ -43,11 +42,28 @@ if(!empty($_GET['resolve'])) {
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$countryCode = $_SERVER['COUNTRY_CODE'] ?? 'XX';
$siteIsPrivate = $cfg->getValue('private.enable', IConfig::T_BOOL);
$loginPermCat = $siteIsPrivate ? $cfg->getValue('private.perm.cat', IConfig::T_STR) : '';
$loginPermVal = $siteIsPrivate ? $cfg->getValue('private.perm.val', IConfig::T_INT) : 0;
$remainingAttempts = UserLoginAttempt::remaining($ipAddress);
$siteIsPrivate = $cfg->getBoolean('private.enable');
if($siteIsPrivate) {
[
'private.perm.cat' => $loginPermCat,
'private.perm.val' => $loginPermVal,
'private.msg' => $sitePrivateMessage,
'private.allow_password_reset' => $canResetPassword,
] = $cfg->getValues([
'private.perm.cat:s',
'private.perm.val:i',
'private.msg:s',
['private.allow_password_reset:b', true],
]);
} else {
$loginPermCat = '';
$loginPermVal = 0;
$sitePrivateMessage = '';
$canResetPassword = true;
}
while(!empty($_POST['login']) && is_array($_POST['login'])) {
if(!CSRF::validateRequest()) {
$notices[] = 'Was unable to verify the request, please try again!';
@ -134,8 +150,6 @@ $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 ? $cfg->getValue('private.msg', IConfig::T_STR) : '';
$canResetPassword = $siteIsPrivate ? $cfg->getValue('private.allow_password_reset', IConfig::T_BOOL, true) : true;
$canRegisterAccount = !$siteIsPrivate;
Template::render('auth.login', [

View file

@ -1,7 +1,6 @@
<?php
namespace Misuzu;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
use Misuzu\Users\UserNotFoundException;
use Misuzu\Users\UserLoginAttempt;
@ -33,8 +32,8 @@ if($userId > 0)
$notices = [];
$ipAddress = $_SERVER['REMOTE_ADDR'];
$siteIsPrivate = $cfg->getValue('private.enable', IConfig::T_BOOL);
$canResetPassword = $siteIsPrivate ? $cfg->getValue('private.allow_password_reset', IConfig::T_BOOL, true) : true;
$siteIsPrivate = $cfg->getBoolean('private.enable');
$canResetPassword = $siteIsPrivate ? $cfg->getBoolean('private.allow_password_reset', true) : true;
$remainingAttempts = UserLoginAttempt::remaining($ipAddress);
while($canResetPassword) {

View file

@ -1,7 +1,6 @@
<?php
namespace Misuzu;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
require_once '../../misuzu.php';
@ -21,8 +20,16 @@ $leaderboardIdLength = strlen($leaderboardId);
$leaderboardYear = $leaderboardIdLength === 4 || $leaderboardIdLength === 6 ? substr($leaderboardId, 0, 4) : null;
$leaderboardMonth = $leaderboardIdLength === 6 ? substr($leaderboardId, 4, 2) : null;
$unrankedForums = !empty($_GET['allow_unranked']) ? [] : $cfg->getValue('forum_leader.unranked.forum', IConfig::T_ARR);
$unrankedTopics = !empty($_GET['allow_unranked']) ? [] : $cfg->getValue('forum_leader.unranked.topic', IConfig::T_ARR);
if(empty($_GET['allow_unranked'])) {
[
'forum_leader.unranked.forum' => $unrankedForums,
'forum_leader.unranked.topic' => $unrankedTopics,
] = $cfg->getValues([
'forum_leader.unranked.forum:a',
'forum_leader.unranked.topic:a',
]);
} else $unrankedForums = $unrankedTopics = [];
$leaderboards = forum_leaderboard_categories();
$leaderboard = forum_leaderboard_listing($leaderboardYear, $leaderboardMonth, $unrankedForums, $unrankedTopics);
@ -31,9 +38,8 @@ $leaderboardName = 'All Time';
if($leaderboardYear) {
$leaderboardName = "Leaderboard {$leaderboardYear}";
if($leaderboardMonth) {
if($leaderboardMonth)
$leaderboardName .= "-{$leaderboardMonth}";
}
}
if($leaderboardMode === 'markdown') {

View file

@ -1,7 +1,6 @@
<?php
namespace Misuzu;
use Misuzu\Config;
use Misuzu\Config\CfgTools;
use Misuzu\Users\User;
@ -13,25 +12,21 @@ if(!User::hasCurrent()
return;
}
$sName = (string)filter_input(INPUT_GET, 'name');
if(!CfgTools::validateName($sName) || !$cfg->hasValue($sName))
throw new \Exception("Config value does not exist.");
if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
$msz->createAuditLog('CONFIG_DELETE', [$sName]);
$cfg->removeValue($sName);
url_redirect('manage-general-settings');
} else {
$sValue = $cfg->getValue($sName);
Template::render('manage.general.setting-delete', [
'conf_var' => [
'name' => $sName,
'type' => CfgTools::type($sValue),
'value' => $sValue,
],
]);
$valueName = (string)filter_input(INPUT_GET, 'name');
$valueInfo = $cfg->getValueInfo($valueName);
if($valueInfo === null) {
echo render_error(404);
return;
}
if($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
$valueName = $valueInfo->getName();
$msz->createAuditLog('CONFIG_DELETE', [$valueName]);
$cfg->removeValues($valueName);
url_redirect('manage-general-settings');
return;
}
Template::render('manage.general.setting-delete', [
'config_value' => $valueInfo,
]);

View file

@ -1,9 +1,7 @@
<?php
namespace Misuzu;
use Misuzu\Config;
use Misuzu\Config\CfgTools;
use Misuzu\Config\IConfig;
use Misuzu\Config\DbConfig;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
@ -14,105 +12,84 @@ if(!User::hasCurrent()
return;
}
$sVar = [
'name' => '',
'type' => '',
'value' => null,
'new' => true,
];
$isNew = true;
$sName = (string)filter_input(INPUT_GET, 'name');
$sType = (string)filter_input(INPUT_GET, 'type');
$sValue = null;
$loadValueInfo = fn() => $cfg->getValueInfo($sName);
if(!empty($sName)) {
if(!CfgTools::validateName($sName))
throw new \Exception("Config key name has invalid format.");
$sVar['name'] = $sName;
$sInfo = $loadValueInfo();
if($sInfo !== null) {
$isNew = false;
$sName = $sInfo->getName();
$sType = $sInfo->getType();
$sValue = $sInfo->getValue();
}
}
$sType = (string)filter_input(INPUT_GET, 'type');
if(!empty($sType)) {
if(!CfgTools::isValidType($sType))
throw new \Exception("Specified type is invalid.");
while($_SERVER['REQUEST_METHOD'] === 'POST' && CSRF::validateRequest()) {
if($isNew) {
$sName = trim((string)filter_input(INPUT_POST, 'conf_name'));
if(!DbConfig::validateName($sName)) {
echo 'Name contains invalid characters.';
break;
}
$sVar['type'] = $sType;
$sVar['value'] = CfgTools::default($sType);
}
if($_SERVER['REQUEST_METHOD'] === 'POST') {
if(!CSRF::validateRequest())
throw new \Exception("Request verification failed.");
if(empty($sName)) {
$sName = (string)filter_input(INPUT_POST, 'conf_name');
if(empty($sName) || !CfgTools::validateName($sName))
throw new \Exception("Config key name has invalid format.");
$sVar['name'] = $sName;
$sType = trim((string)filter_input(INPUT_POST, 'conf_type'));
if(!in_array($sType, ['string', 'int', 'float', 'bool', 'array'])) {
echo 'Invalid type specified.';
break;
}
}
$sLogAction = 'CONFIG_CREATE';
if($cfg->hasValue($sName)) {
$sType = CfgTools::type($cfg->getValue($sName));
$sVar['new'] = false;
$sLogAction = 'CONFIG_UPDATE';
} elseif(empty($sType)) {
$sType = (string)filter_input(INPUT_POST, 'conf_type');
if(empty($sType) || !CfgTools::isValidType($sType))
throw new \Exception("Specified type is invalid.");
}
$sVar['type'] = $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 = (string)$fv;
if(str_starts_with($fv, 's:')) {
$fv = substr($fv, 2);
} elseif(str_starts_with($fv, 'i:')) {
$fv = (int)substr($fv, 2);
} elseif(str_starts_with($fv, 'b:')) {
$fv = strtolower(substr($fv, 2));
$fv = $fv !== 'false' && $fv !== '0' && $fv !== '';
}
$sValue[] = $fv;
}
$applyFunc = $cfg->setArray(...);
$sValue = [];
$sRaw = filter_input(INPUT_POST, 'conf_value', FILTER_DEFAULT, FILTER_REQUIRE_ARRAY);
foreach($sRaw as $rValue) {
if(strpos($rValue, ':') === 1) {
$rType = $rValue[0];
$rValue = substr($rValue, 2);
$sValue[] = match($rType) {
's' => $rValue,
'i' => (int)$rValue,
'd' => (float)$rValue,
'f' => (float)$rValue,
'b' => $rValue !== 'false' && $rValue !== '0' && $rValue !== '',
default => '',
};
} else
$sValue[] = $rValue;
}
} elseif($sType === 'boolean') {
} elseif($sType === 'bool') {
$sValue = !empty($_POST['conf_value']);
$applyFunc = $cfg->setBoolean(...);
} else {
$sValue = (string)filter_input(INPUT_POST, 'conf_value');
if($sType === 'integer')
$sValue = filter_input(INPUT_POST, 'conf_value');
if($sType === 'int') {
$applyFunc = $cfg->setInteger(...);
$sValue = (int)$sValue;
} elseif($sType === 'float') {
$applyFunc = $cfg->setFloat(...);
$sValue = (float)$sValue;
} else
$applyFunc = $cfg->setString(...);
}
$sVar['value'] = $sValue;
$msz->createAuditLog($sLogAction, [$sName]);
$cfg->setValue($sName, $sValue);
$msz->createAuditLog($isNew ? 'CONFIG_CREATE' : 'CONFIG_UPDATE', [$sName]);
$applyFunc($sName, $sValue);
url_redirect('manage-general-settings');
return;
}
if($cfg->hasValue($sName)) {
$sVar['new'] = false;
$sValue = $cfg->getValue($sName);
$sVar['type'] = $sType = CfgTools::type($sValue);
if($sType === IConfig::T_ARR)
foreach($sValue as $fk => $fv)
$sValue[$fk] = ['integer' => 'i', 'string' => 's', 'boolean' => 'b'][gettype($fv)] . ':' . $fv;
$sVar['value'] = $sValue;
}
if($sType === 'array' && !empty($sValue))
foreach($sValue as $key => $value)
$sValue[$key] = gettype($value)[0] . ':' . $value;
Template::render('manage.general.setting', [
'conf_var' => $sVar,
'config_new' => $isNew,
'config_name' => $sName,
'config_type' => $sType,
'config_value' => $sValue,
]);

View file

@ -1,9 +1,6 @@
<?php
namespace Misuzu;
use Misuzu\Config;
use Misuzu\Config\CfgTools;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
require_once '../../../misuzu.php';
@ -14,18 +11,10 @@ if(!User::hasCurrent()
return;
}
$hidden = $cfg->getValue('settings.hidden', IConfig::T_ARR, []);
$vars = [];
foreach($cfg->getNames() as $key) {
$var = $cfg->getValue($key);
$vars[] = [
'key' => $key,
'type' => CfgTools::type($var),
'value' => in_array($key, $hidden) ? '*** hidden ***' : json_encode($var),
];
}
$hidden = $cfg->getArray('settings.hidden');
$vars = $cfg->getAllValueInfos();
Template::render('manage.general.settings', [
'conf_vars' => $vars,
'config_vars' => $vars,
'config_hidden' => $hidden,
]);

View file

@ -1,8 +1,6 @@
<?php
namespace Misuzu;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
use Misuzu\Users\UserRole;
use Misuzu\Users\UserRoleNotFoundException;
@ -49,7 +47,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 = $cfg->getValue('site.name', IConfig::T_STR, 'Misuzu');
$tfaIssuer = $cfg->getString('site.name', 'Misuzu');
$tfaQrcode = (new QRCode(new QROptions([
'version' => 5,
'outputType' => QRCode::OUTPUT_IMAGE_JPG,

View file

@ -1,32 +0,0 @@
<?php
namespace Misuzu;
use Misuzu\Config\IConfig;
final class Config {
private static IConfig $config;
public static function init(IConfig $config): void {
self::$config = $config;
}
public static function keys(): array {
return self::$config->getNames();
}
public static function get(string $key, string $type = IConfig::T_ANY, $default = null): mixed {
return self::$config->getValue($key, $type, $default);
}
public static function has(string $key): bool {
return self::$config->hasValue($key);
}
public static function set(string $key, $value, bool $soft = false): void {
self::$config->setValue($key, $value, !$soft);
}
public static function remove(string $key, bool $soft = false): void {
self::$config->removeValue($key, !$soft);
}
}

View file

@ -1,32 +0,0 @@
<?php
namespace Misuzu\Config;
final class CfgTools {
public static function type($value): string {
return gettype($value);
}
public static function isValidType(string $type): bool {
return $type === IConfig::T_ARR
|| $type === IConfig::T_BOOL
|| $type === IConfig::T_INT
|| $type === IConfig::T_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;
}
private const DEFAULTS = [
IConfig::T_ANY => null,
IConfig::T_STR => '',
IConfig::T_INT => 0,
IConfig::T_BOOL => false,
IConfig::T_ARR => [],
];
public static function default(string $type) {
return self::DEFAULTS[$type] ?? null;
}
}

View file

@ -1,78 +1,327 @@
<?php
namespace Misuzu\Config;
use RuntimeException;
use InvalidArgumentException;
use Index\Data\DataException;
use Index\Data\IDbConnection;
use Index\Data\IDbStatement;
use Index\Data\IDbResult;
use Misuzu\DbStatementCache;
use Misuzu\Pagination;
class DbConfig implements IConfig {
private IDbConnection $dbConn;
private DbStatementCache $cache;
private array $values = [];
private ?IDbStatement $stmtSet = null;
private ?IDbStatement $stmtDelete = null;
public function __construct(IDbConnection $dbConn) {
$this->dbConn = $dbConn;
$this->cache = new DbStatementCache($dbConn);
}
public function reload(): void {
$this->values = [];
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;
}
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) { }
private static function whereInList(int $count): string {
return implode(', ', array_fill(0, $count, '?'));
}
public function reset(): void {
$this->values = [];
}
public function unload(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
foreach($names as $name)
unset($this->values[$name]);
}
public function scopeTo(string $prefix): IConfig {
return new ScopedConfig($this, $prefix);
}
public function getNames(): array {
return array_keys($this->values);
public function hasValues(string|array $names): bool {
if(empty($names))
return true;
if(is_string($names))
$names = [$names];
$cachedNames = array_keys($this->values);
$names = array_diff($names, $cachedNames);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'SELECT COUNT(*) FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
$result = $stmt->getResult();
if($result->next())
return $result->getInteger(0) >= $nameCount;
}
return true;
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
$value = $this->values[$name] ?? null;
public function removeValues(string|array $names): void {
if(empty($names))
return;
if(is_string($names))
$names = [$names];
if($type !== IConfig::T_ANY && CfgTools::type($value) !== $type)
$value = null;
foreach($names as $name)
unset($this->values[$name]);
return $value ?? $default ?? CfgTools::default($type);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'DELETE FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
}
public function hasValue(string $name): bool {
return array_key_exists($name, $this->values);
public function getAllValueInfos(?Pagination $pagination = null): array {
$this->reset();
$infos = [];
$hasPagination = $pagination !== null;
$query = 'SELECT config_name, config_value FROM msz_config';
if($hasPagination)
$query .= ' LIMIT ? RANGE ?';
$stmt = $this->cache->get($query);
if($hasPagination) {
$stmt->addParameter(1, $pagination->getRange());
$stmt->addParameter(2, $pagination->getOffset());
}
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
return $infos;
}
public function setValue(string $name, $value, bool $save = true): void {
public function getValueInfos(string|array $names): array {
if(empty($names))
return [];
if(is_string($names))
$names = [$names];
$infos = [];
$skip = [];
foreach($names as $name)
if(array_key_exists($name, $this->values)) {
$infos[] = $this->values[$name];
$skip[] = $name;
}
$names = array_diff($names, $skip);
if(!empty($names)) {
// array_diff preserves keys, the for() later would fuck up without it
$names = array_values($names);
$nameCount = count($names);
$stmt = $this->cache->get(sprintf(
'SELECT config_name, config_value FROM msz_config WHERE config_name IN (%s)',
self::whereInList($nameCount)
));
for($i = 0; $i < $nameCount; ++$i)
$stmt->addParameter($i + 1, $names[$i]);
$stmt->execute();
$result = $stmt->getResult();
while($result->next()) {
$name = $result->getString(0);
$infos[] = $this->values[$name] = new DbConfigValueInfo($result);
}
}
return $infos;
}
public function getValueInfo(string $name): ?IConfigValueInfo {
$infos = $this->getValueInfos($name);
return empty($infos) ? null : $infos[0];
}
public function getValues(array $specs): array {
$names = [];
foreach($specs as $key => $spec) {
if(is_string($spec)) {
$name = $spec;
$default = null;
} elseif(is_array($spec) && !empty($spec)) {
$name = $spec[0];
$default = $spec[1] ?? null;
} else
throw new InvalidArgumentException('$specs array contains an invalid entry.');
if(($colon = strpos($name, ':')) !== false) {
$type = substr($name, $colon + 1, 1);
$name = substr($name, 0, $colon);
} else $type = '';
$names[] = $name;
$specs[$key] = [
'name' => $name,
'type' => $type,
'default' => $default,
];
}
$infos = $this->getValueInfos($names);
$results = [];
foreach($specs as $spec) {
foreach($infos as $infoTest)
if($infoTest->getName() === $spec['name']) {
$info = $infoTest;
break;
}
if(!isset($info)) {
$defaultValue = $spec['default'] ?? null;
if($spec['type'] !== '')
settype($defaultValue, match($spec['type']) {
's' => 'string',
'a' => 'array',
'i' => 'int',
'b' => 'bool',
'f' => 'float',
'd' => 'double',
default => throw new RuntimeException('Invalid type letter encountered.'),
});
$results[$spec['name']] = $defaultValue;
continue;
}
$results[$spec['name']] = match($spec['type']) {
's' => $info->getString(),
'a' => $info->getArray(),
'i' => $info->getInteger(),
'b' => $info->getBoolean(),
'f' => $info->getFloat(),
'd' => $info->getFloat(),
'' => $info->getValue(),
default => throw new RuntimeException('Unknown type encountered in $specs.'),
};
unset($info);
}
return $results;
}
public function getString(string $name, string $default = ''): string {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isString() ? $valueInfo->getString() : $default;
}
public function getInteger(string $name, int $default = 0): int {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isInteger() ? $valueInfo->getInteger() : $default;
}
public function getFloat(string $name, float $default = 0): float {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isFloat() ? $valueInfo->getFloat() : $default;
}
public function getBoolean(string $name, bool $default = false): bool {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isBoolean() ? $valueInfo->getBoolean() : $default;
}
public function getArray(string $name, array $default = []): array {
$valueInfo = $this->getValueInfo($name);
return $valueInfo?->isArray() ? $valueInfo->getArray() : $default;
}
private function setValue(string $name, $value): void {
$this->values[$name] = $value;
$value = serialize($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();
}
$stmt = $this->cache->get('REPLACE INTO msz_config (config_name, config_value) VALUES (?, ?)');
$stmt->addParameter(1, $name);
$stmt->addParameter(2, $value);
$stmt->execute();
}
public function removeValue(string $name, bool $save = true): void {
unset($this->values[$name]);
public function setValues(array $values): void {
if(empty($values))
return;
if($save) {
if($this->stmtDelete === null)
$this->stmtDelete = $this->dbConn->prepare('DELETE FROM msz_config WHERE config_name = ?');
$valueCount = count($values);
$this->stmtDelete->reset();
$this->stmtDelete->addParameter(1, $name);
$this->stmtDelete->execute();
$stmt = $this->cache->get(sprintf(
'REPLACE INTO msz_config (config_name, config_value) VALUES %s',
implode(', ', array_fill(0, $valueCount, '(?, ?)'))
));
$args = 0;
foreach($values as $name => $value) {
if(!self::validateName($name))
throw new InvalidArgumentException('Invalid name encountered in $values.');
if(is_array($value)) {
foreach($value as $entry)
if(!is_scalar($entry))
throw new InvalidArgumentException('An array value in $values contains a non-scalar type.');
} elseif(!is_scalar($value))
throw new InvalidArgumentException('Invalid value type encountered in $values.');
$stmt->addParameter(++$args, $name);
$stmt->addParameter(++$args, serialize($value));
}
$stmt->execute();
}
public function setString(string $name, string $value): void {
$this->setValues([$name => $value]);
}
public function setInteger(string $name, int $value): void {
$this->setValues([$name => $value]);
}
public function setFloat(string $name, float $value): void {
$this->setValues([$name => $value]);
}
public function setBoolean(string $name, bool $value): void {
$this->setValues([$name => $value]);
}
public function setArray(string $name, array $value): void {
$this->setValues([$name => $value]);
}
}

View file

@ -0,0 +1,92 @@
<?php
namespace Misuzu\Config;
use RuntimeException;
use Index\Data\IDbResult;
class DbConfigValueInfo implements IConfigValueInfo {
private string $name;
private string $value;
public function __construct(IDbResult $result) {
$this->name = $result->getString(0);
$this->value = $result->getString(1);
}
public function getName(): string {
return $this->name;
}
public function getType(): string {
return match($this->value[0]) {
's' => 'string',
'a' => 'array',
'i' => 'int',
'b' => 'bool',
'd' => 'float',
};
}
public function isString(): bool {
return $this->value[0] === 's';
}
public function isInteger(): bool {
return $this->value[0] === 'i';
}
public function isFloat(): bool {
return $this->value[0] === 'd';
}
public function isBoolean(): bool {
return $this->value[0] === 'b';
}
public function isArray(): bool {
return $this->value[0] === 'a';
}
public function getValue(): mixed {
return unserialize($this->value);
}
public function getString(): string {
$value = $this->getValue();
if(!is_string($value))
throw new RuntimeException('Value is not a string.');
return $value;
}
public function getInteger(): int {
$value = $this->getValue();
if(!is_int($value))
throw new RuntimeException('Value is not an integer.');
return $value;
}
public function getFloat(): float {
$value = $this->getValue();
if(!is_float($value))
throw new RuntimeException('Value is not a floating point number.');
return $value;
}
public function getBoolean(): bool {
$value = $this->getValue();
if(!is_bool($value))
throw new RuntimeException('Value is not a boolean.');
return $value;
}
public function getArray(): array {
$value = $this->getValue();
if(!is_array($value))
throw new RuntimeException('Value is not an array.');
return $value;
}
public function __toString(): string {
return $this->isArray() ? implode(', ', $this->getArray()) : (string)$this->getValue();
}
}

View file

@ -1,23 +1,29 @@
<?php
namespace Misuzu\Config;
// getValue (and hasValue?) should probably be replaced with something that allows grouped loading
// that way the entire config doesn't have to be kept in memory on every request
// this probably has to be delayed until the backwards compat static Config object isn't needed anymore
// otherwise there'd be increased overhead
// bulk operations for setValue and removeValue would also be cool
use Misuzu\Pagination;
interface IConfig {
public const T_ANY = '';
public const T_STR = 'string';
public const T_INT = 'integer';
public const T_BOOL = 'boolean';
public const T_ARR = 'array';
public function scopeTo(string $prefix): IConfig;
public function getNames(): array;
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed;
public function hasValue(string $name): bool;
public function setValue(string $name, $value, bool $save = true): void;
public function removeValue(string $name, bool $save = true): void;
public function hasValues(string|array $names): bool;
public function removeValues(string|array $names): void;
public function getAllValueInfos(?Pagination $pagination = null): array;
public function getValueInfos(string|array $names): array;
public function getValueInfo(string $name): ?IConfigValueInfo;
public function getValues(array $specs): array;
public function getString(string $name, string $default = ''): string;
public function getInteger(string $name, int $default = 0): int;
public function getFloat(string $name, float $default = 0): float;
public function getBoolean(string $name, bool $default = false): bool;
public function getArray(string $name, array $default = []): array;
public function setValues(array $values): void;
public function setString(string $name, string $value): void;
public function setInteger(string $name, int $value): void;
public function setFloat(string $name, float $value): void;
public function setBoolean(string $name, bool $value): void;
public function setArray(string $name, array $value): void;
}

View file

@ -0,0 +1,22 @@
<?php
namespace Misuzu\Config;
use Stringable;
interface IConfigValueInfo extends Stringable {
public function getName(): string;
public function getType(): string;
public function isString(): bool;
public function isInteger(): bool;
public function isFloat(): bool;
public function isBoolean(): bool;
public function isArray(): bool;
public function getValue(): mixed;
public function getString(): string;
public function getInteger(): int;
public function getFloat(): float;
public function getBoolean(): bool;
public function getArray(): array;
}

View file

@ -2,12 +2,13 @@
namespace Misuzu\Config;
use InvalidArgumentException;
use Misuzu\Pagination;
class ScopedConfig implements IConfig {
private IConfig $config;
private string $prefix;
private int $prefixLength;
// update this to : at some point, would need adjustment of the live site's config
private const SCOPE_CHAR = '.';
public function __construct(IConfig $config, string $prefix) {
@ -18,41 +19,120 @@ class ScopedConfig implements IConfig {
$this->config = $config;
$this->prefix = $prefix;
$this->prefixLength = strlen($prefix);
}
private function getName(string $name): string {
private function prefixNames(string|array $names): array {
if(is_string($names))
return [$this->prefix . $name];
foreach($names as $key => $name)
$names[$key] = $this->prefix . $name;
return $names;
}
private function prefixName(string $name): string {
return $this->prefix . $name;
}
public function scopeTo(string $prefix): IConfig {
return $this->config->scopeTo($this->getName($prefix));
return $this->config->scopeTo($this->prefixName($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 hasValues(string|array $names): bool {
$this->config->hasValues($this->prefixNames($names));
}
public function getValue(string $name, string $type = IConfig::T_ANY, $default = null): mixed {
return $this->config->getValue($this->getName($name), $type, $default);
public function removeValues(string|array $names): void {
$this->config->removeValues($this->prefixNames($names));
}
public function hasValue(string $name): bool {
return $this->config->hasValue($this->getName($name));
public function getAllValueInfos(?Pagination $pagination = null): array {
$infos = $this->config->getAllValueInfos($pagination);
foreach($infos as $key => $info)
$infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
return $infos;
}
public function setValue(string $name, $value, bool $save = true): void {
$this->config->setValue($this->getName($name), $value, $save);
public function getValueInfos(string|array $names): array {
$infos = $this->config->getValueInfos($this->prefixNames($names));
foreach($infos as $key => $info)
$infos[$key] = new ScopedConfigValueInfo($info, $this->prefixLength);
return $infos;
}
public function removeValue(string $name, bool $save = true): void {
$this->config->removeValue($this->getName($name), $save);
public function getValueInfo(string $name): ?IConfigValueInfo {
$info = $this->config->getValueInfo($this->prefixName($name));
if($info !== null)
$info = new ScopedConfigValueInfo($info, $this->prefixLength);
return $info;
}
public function getValues(array $specs): array {
foreach($specs as $key => $spec) {
if(is_string($spec))
$specs[$key] = $this->prefixName($spec);
elseif(is_array($spec) && !empty($spec))
$specs[$key][0] = $this->prefixName($spec[0]);
else
throw new InvalidArgumentException('$specs array contains an invalid entry.');
}
$results = [];
foreach($this->config->getValues($specs) as $name => $result)
$results[substr($name, $this->prefixLength)] = $result;
return $results;
}
public function getString(string $name, string $default = ''): string {
return $this->config->getString($this->prefixName($name), $default);
}
public function getInteger(string $name, int $default = 0): int {
return $this->config->getInteger($this->prefixName($name), $default);
}
public function getFloat(string $name, float $default = 0): float {
return $this->config->getFloat($this->prefixName($name), $default);
}
public function getBoolean(string $name, bool $default = false): bool {
return $this->config->getBoolean($this->prefixName($name), $default);
}
public function getArray(string $name, array $default = []): array {
return $this->config->getArray($this->prefixName($name), $default);
}
public function setValues(array $values): void {
if(empty($values))
return;
$prefixed = [];
foreach($values as $name => $value)
$prefixed[$this->prefixName($name)] = $value;
$this->config->setValues($values);
}
public function setString(string $name, string $value): void {
$this->config->setString($this->prefixName($name), $value);
}
public function setInteger(string $name, int $value): void {
$this->config->setInteger($this->prefixName($name), $value);
}
public function setFloat(string $name, float $value): void {
$this->config->setFloat($this->prefixName($name), $value);
}
public function setBoolean(string $name, bool $value): void {
$this->config->setBoolean($this->prefixName($name), $value);
}
public function setArray(string $name, array $value): void {
$this->config->setArray($this->prefixName($name), $value);
}
}

View file

@ -0,0 +1,72 @@
<?php
namespace Misuzu\Config;
class ScopedConfigValueInfo implements IConfigValueInfo {
private IConfigValueInfo $info;
private int $prefixLength;
public function __construct(IConfigValueInfo $info, int $prefixLength) {
$this->info = $info;
$this->prefixLength = $prefixLength;
}
public function getName(): string {
return substr($this->info->getName(), $this->prefixLength);
}
public function getRealName(): string {
return $this->info->getName();
}
public function getType(): string {
return $this->info->getType();
}
public function isString(): bool {
return $this->info->isString();
}
public function isInteger(): bool {
return $this->info->isInteger();
}
public function isFloat(): bool {
return $this->info->isFloat();
}
public function isBoolean(): bool {
return $this->info->isBoolean();
}
public function isArray(): bool {
return $this->info->isArray();
}
public function getValue(): mixed {
return $this->info->getValue();
}
public function getString(): string {
return $this->info->getString();
}
public function getInteger(): int {
return $this->info->getInteger();
}
public function getFloat(): float {
return $this->info->getFloat();
}
public function getBoolean(): bool {
return $this->info->getBoolean();
}
public function getArray(): array {
return $this->info->getArray();
}
public function __toString(): string {
return (string)$this->info;
}
}

View file

@ -535,7 +535,7 @@ function forum_get_user_most_active_category_info(int $userId): ?object {
$getActiveForum = \Misuzu\DB::prepare(sprintf(
'SELECT forum_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = :user AND post_deleted IS NULL AND forum_id NOT IN (%s) GROUP BY forum_id ORDER BY post_count DESC LIMIT 1',
implode(',', $cfg->getValue('forum_leader.unranked.forum', \Misuzu\Config\IConfig::T_ARR))
implode(',', $cfg->getArray('forum_leader.unranked.forum'))
));
$getActiveForum->bind('user', $userId);

View file

@ -711,7 +711,7 @@ function forum_get_user_most_active_topic_info(int $userId): ?object {
$getActiveForum = \Misuzu\DB::prepare(sprintf(
'SELECT topic_id, COUNT(*) AS post_count FROM msz_forum_posts WHERE user_id = :user AND post_deleted IS NULL AND forum_id NOT IN (%s) GROUP BY topic_id ORDER BY post_count DESC LIMIT 1',
implode(',', $cfg->getValue('forum_leader.unranked.forum', \Misuzu\Config\IConfig::T_ARR))
implode(',', $cfg->getArray('forum_leader.unranked.forum'))
));
$getActiveForum->bind('user', $userId);

View file

@ -3,8 +3,6 @@ namespace Misuzu\Http\Handlers;
use ErrorException;
use RuntimeException;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\Pagination;
use Misuzu\Template;
use Misuzu\Comments\CommentsEx;
@ -119,11 +117,12 @@ class ChangelogHandler extends Handler {
}
private function createFeed(string $feedMode): Feed {
$siteName = $this->context->getConfig()->getString('site.name', 'Misuzu');
$changes = $this->context->getChangelog()->getAllChanges(pagination: new Pagination(10));
$feed = (new Feed)
->setTitle(Config::get('site.name', IConfig::T_STR, 'Misuzu') . ' » Changelog')
->setDescription('Live feed of changes to ' . Config::get('site.name', IConfig::T_STR, 'Misuzu') . '.')
->setTitle($siteName . ' » Changelog')
->setDescription('Live feed of changes to ' . $siteName . '.')
->setContentUrl(url_prefix(false) . url('changelog-index'))
->setFeedUrl(url_prefix(false) . url("changelog-feed-{$feedMode}"));

View file

@ -2,8 +2,6 @@
namespace Misuzu\Http\Handlers;
use RuntimeException;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\DB;
use Misuzu\Pagination;
use Misuzu\Template;
@ -21,13 +19,22 @@ final class HomeHandler extends Handler {
}
public function landing($response, $request): void {
$linkedData = Config::get('social.embed_linked', IConfig::T_BOOL)
? [
'name' => Config::get('site.name', IConfig::T_STR, 'Misuzu'),
'url' => Config::get('site.url', IConfig::T_STR),
'logo' => Config::get('site.ext_logo', IConfig::T_STR),
'same_as' => Config::get('social.linked', IConfig::T_ARR),
] : null;
$config = $this->context->getConfig();
if($config->getBoolean('social.embed_linked')) {
$ldr = $config->getValues([
['site.name:s', 'Misuzu'],
'site.url:s',
'site.ext_logo:s',
'social.linked:a'
]);
$linkedData = [
'name' => $ldr['site.name'],
'url' => $ldr['site.url'],
'logo' => $ldr['site.ext_logo'],
'same_as' => $ldr['social.linked'],
];
} else $linkedData = null;
$featuredNews = $this->context->getNews()->getAllPosts(
onlyFeatured: true,
@ -55,7 +62,7 @@ final class HomeHandler extends Handler {
)->fetchAll();
// TODO: don't hardcode forum ids
$featuredForums = Config::get('landing.forum_categories', IConfig::T_ARR);
$featuredForums = $config->getArray('landing.forum_categories');
$popularTopics = [];
$activeTopics = [];

View file

@ -2,13 +2,11 @@
namespace Misuzu\Http\Handlers;
use RuntimeException;
use Misuzu\Config;
use Misuzu\DB;
use Misuzu\Pagination;
use Misuzu\Template;
use Misuzu\Comments\CommentsCategory;
use Misuzu\Comments\CommentsEx;
use Misuzu\Config\IConfig;
use Misuzu\Feeds\Feed;
use Misuzu\Feeds\FeedItem;
use Misuzu\Feeds\AtomFeedSerializer;
@ -154,9 +152,10 @@ final class NewsHandler extends Handler {
private function createFeed(string $feedMode, ?NewsCategoryInfo $categoryInfo, array $posts): Feed {
$hasCategory = $categoryInfo !== null;
$siteName = $this->context->getConfig()->getString('site.name', 'Misuzu');
$feed = (new Feed)
->setTitle(Config::get('site.name', IConfig::T_STR, 'Misuzu') . ' » ' . ($hasCategory ? $categoryInfo->getName() : 'Featured News'))
->setTitle($siteName . ' » ' . ($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}")));

View file

@ -2,6 +2,7 @@
namespace Misuzu;
use InvalidArgumentException;
use Misuzu\Config\IConfig;
use Symfony\Component\Mime\Email as SymfonyMessage;
use Symfony\Component\Mime\Address as SymfonyAddress;
use Symfony\Component\Mailer\Mailer as SymfonyMailer;
@ -11,25 +12,29 @@ use Symfony\Component\Mailer\Exception\TransportExceptionInterface;
final class Mailer {
private const TEMPLATE_PATH = MSZ_ROOT . '/config/emails/%s.txt';
private static $dsn = 'null://null';
private static IConfig $config;
private static $transport = null;
private static string $senderName = 'Flashii';
private static string $senderAddr = 'sys@flashii.net';
public static function init(string $method, array $config): void {
if($method !== 'smtp') {
self::$dsn = 'null://null';
return;
}
public static function init(IConfig $config): void {
self::$config = $config;
}
$dsn = $method;
private static function createDsn(): string {
$config = self::$config->getValues([
'method:s',
'host:s',
['port:i', 25],
'username:s',
'password:s',
]);
// guess this is applied automatically based on the port?
//if(!empty($config['encryption']))
// $dsn .= 't';
if($config['method'] !== 'smtp')
return 'null://null';
$dsn .= '://';
$dsn = $config['method'] . '://';
if(!empty($config['username'])) {
$dsn .= $config['username'];
@ -44,18 +49,18 @@ final class Mailer {
$dsn .= ':';
$dsn .= $config['port'] ?? 25;
self::$dsn = $dsn;
if(!empty($config['sender.name']))
self::$senderName = $config['sender.name'];
if(!empty($config['sender_name']))
self::$senderName = $config['sender_name'];
if(!empty($config['sender.address']))
self::$senderAddr = $config['sender.address'];
if(!empty($config['sender_addr']))
self::$senderAddr = $config['sender_addr'];
return $dsn;
}
public static function getTransport() {
if(self::$transport === null)
self::$transport = SymfonyTransport::fromDsn(self::$dsn);
self::$transport = SymfonyTransport::fromDsn(self::createDsn());
return self::$transport;
}
@ -65,11 +70,16 @@ final class Mailer {
break;
}
$config = self::$config->getValues([
'sender.name:s',
'sender.address:s',
]);
$message = new SymfonyMessage;
$message->from(new SymfonyAddress(
self::$senderAddr,
self::$senderName
$config['sender.address'],
$config['sender.name']
));
if($bcc)

View file

@ -24,10 +24,10 @@ final class SharpChatRoutes {
$this->config = $config;
$this->emotes = $emotes;
$hashKey = $this->config->getValue('hashKey', IConfig::T_STR, '');
$hashKey = $this->config->getString('hashKey', '');
if(empty($hashKey)) {
$hashKeyPath = $this->config->getValue('hashKeyPath', IConfig::T_STR, '');
$hashKeyPath = $this->config->getString('hashKeyPath', '');
if(is_file($hashKeyPath))
$this->hashKey = file_get_contents($hashKeyPath);
} else {
@ -96,7 +96,7 @@ final class SharpChatRoutes {
public function getLogin($response, $request): void {
$currentUser = User::getCurrent();
$configKey = $request->hasParam('legacy') ? 'chatPath.legacy' : 'chatPath.normal';
$chatPath = $this->config->getValue($configKey, IConfig::T_STR, '/');
$chatPath = $this->config->getString($configKey, '/');
$response->redirect(
$currentUser === null
@ -111,7 +111,7 @@ final class SharpChatRoutes {
$originHost = strtolower(parse_url($origin, PHP_URL_HOST) ?? '');
if(!empty($originHost) && $originHost !== $host) {
$whitelist = $this->config->getValue('origins', IConfig::T_ARR, []);
$whitelist = $this->config->getArray('origins', []);
if(!in_array($originHost, $whitelist))
return 403;

View file

@ -1,8 +1,6 @@
<?php
namespace Misuzu\Users\Assets;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\Imaging\Image;
use Misuzu\Users\User;
@ -20,13 +18,15 @@ class UserAvatarAsset extends UserImageAsset implements UserAssetScalableInterfa
private const MAX_BYTES = 1000000;
public function getMaxWidth(): int {
return Config::get('avatar.max_res', IConfig::T_INT, self::MAX_RES);
global $cfg;
return $cfg->getInteger('avatar.max_res', self::MAX_RES);
}
public function getMaxHeight(): int {
return $this->getMaxWidth();
}
public function getMaxBytes(): int {
return Config::get('avatar.max_size', IConfig::T_INT, self::MAX_BYTES);
global $cfg;
return $cfg->getInteger('avatar.max_size', self::MAX_BYTES);
}
public function getUrl(): string {

View file

@ -2,8 +2,6 @@
namespace Misuzu\Users\Assets;
use InvalidArgumentException;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
// attachment and attributes are to be stored in the same byte
@ -49,13 +47,16 @@ class UserBackgroundAsset extends UserImageAsset {
}
public function getMaxWidth(): int {
return Config::get('background.max_width', IConfig::T_INT, self::MAX_WIDTH);
global $cfg;
return $cfg->getInteger('background.max_width', self::MAX_WIDTH);
}
public function getMaxHeight(): int {
return Config::get('background.max_height', IConfig::T_INT, self::MAX_HEIGHT);
global $cfg;
return $cfg->getInteger('background.max_height', self::MAX_HEIGHT);
}
public function getMaxBytes(): int {
return Config::get('background.max_size', IConfig::T_INT, self::MAX_BYTES);
global $cfg;
return $cfg->getInteger('background.max_size', self::MAX_BYTES);
}
public function getUrl(): string {

View file

@ -1,8 +1,6 @@
<?php
namespace Misuzu\Users\Assets;
use Misuzu\Config;
use Misuzu\Config\IConfig;
use Misuzu\Users\User;
class UserImageAssetFileCreationFailedException extends UserAssetException {}
@ -83,7 +81,7 @@ abstract class UserImageAsset implements UserImageAssetInterface {
}
public function getStoragePath(): string {
return Config::get('storage.path', IConfig::T_STR, MSZ_ROOT . DIRECTORY_SEPARATOR . 'store');
return MSZ_ROOT . DIRECTORY_SEPARATOR . 'store';
}
public function getPath(): string {

View file

@ -2,7 +2,7 @@
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_csrf, input_text, input_checkbox, input_file, input_select, input_colour %}
{% set title = ('Removing setting ' ~ conf_var.name) %}
{% set title = ('Removing setting ' ~ config_value.name) %}
{% block manage_content %}
<div class="container manage-settings">
@ -12,7 +12,7 @@
Are you sure you want to delete this setting? It cannot be recovered.
</div>
<form method="post" action="{{ url('manage-general-setting-delete', {'name': conf_var.name}) }}" class="manage-setting">
<form method="post" action="{{ url('manage-general-setting-delete', {'name': config_value.name}) }}" class="manage-setting">
{{ input_csrf() }}
<div class="manage-settings-list-container">
@ -20,13 +20,13 @@
<tbody>
<tr class="manage-list-setting">
<td class="manage-list-setting-key">
<div class="manage-list-setting-key-text">{{ conf_var.name }}</div>
<div class="manage-list-setting-key-text">{{ config_value.name }}</div>
</td>
<td class="manage-list-setting-type manage-list-setting-type--{{ conf_var.type }}">
<div class="manage-list-setting-type-text">{{ conf_var.type }}</div>
<td class="manage-list-setting-type manage-list-setting-type--{{ config_value.type }}">
<div class="manage-list-setting-type-text">{{ config_value.type }}</div>
</td>
<td class="manage-list-setting-value">
<div class="manage-list-setting-value-text">{{ conf_var.value|json_encode }}</div>
<div class="manage-list-setting-value-text">{{ config_value }}</div>
</td>
</tr>
</tbody>

View file

@ -2,44 +2,44 @@
{% from 'macros.twig' import container_title %}
{% from '_layout/input.twig' import input_csrf, input_text, input_checkbox, input_file, input_select, input_colour %}
{% set title = conf_var.name is empty ? 'Adding a new setting' : ((conf_var.new ? 'Adding ' : 'Editing ') ~ ' setting ' ~ conf_var.name) %}
{% set title = config_new ? 'Adding a new setting' : ((config_new ? 'Adding ' : 'Editing ') ~ ' setting ' ~ config_name) %}
{% block manage_content %}
<div class="container manage-settings">
{{ container_title('<i class="fas fa-cogs fa-fw"></i> ' ~ title) }}
<form method="post" action="{{ url('manage-general-setting', {'name': conf_var.name}) }}" class="manage-setting" id="-msz-manage-setting-form">
<form method="post" action="{{ url('manage-general-setting', {'name': config_name|default()}) }}" class="manage-setting" id="-msz-manage-setting-form">
{{ input_csrf() }}
{% if conf_var.new %}
{% if config_new %}
<label class="manage-setting-field">
<div class="manage-setting-field-name">Name</div>
{{ input_text('conf_name', 'manage__emote__field__value', conf_var.name, 'text', null, false, {'id': '-msz-manage-setting-name'}) }}
{{ input_text('conf_name', 'manage__emote__field__value', config_name|default(), 'text', null, false, {'id': '-msz-manage-setting-name'}) }}
</label>
<label class="manage-setting-field">
<div class="manage-setting-field-name">Type</div>
{{ input_select(
'conf_type',
['', 'string', 'integer', 'boolean', 'array'],
conf_var.type, null, null, true, 'manage__emote__field__value',
['', 'string', 'int', 'bool', 'float', 'array'],
config_type, null, null, true, 'manage__emote__field__value',
{'onchange': ('location.assign("' ~ url('manage-general-setting', {'name': '-name-', 'type': '-type-'}) ~ '".replace("-name-", document.getElementById("-msz-manage-setting-name").value).replace("-type-", this.value))')}
) }}
</label>
{% endif %}
{% if conf_var.type is not empty %}
{% if config_type is not empty %}
<label class="manage-setting-field">
<div class="manage-setting-field-name">Value</div>
{% if conf_var.type == 'array' %}
{% if config_type == 'array' %}
<div class="manage-setting-array">
<div class="manage-setting-array-warning">
<noscript><div class="warning"><div class="warning__content">Touching this without Javascript will destroy everything.</div></div></noscript>
</div>
<div class="manage-setting-array-select">
<select class="input__select" multiple id="-msz-manage-setting-array" name="conf_value[]">
{% for entry in conf_var.value %}
{% for entry in config_value %}
<option>{{ entry }}</option>
{% endfor %}
</select>
@ -86,12 +86,14 @@
}
});
</script>
{% elseif conf_var.type == 'string' %}
{{ input_text('conf_value', 'manage-setting-field-value', conf_var.value, 'text') }}
{% elseif conf_var.type == 'integer' %}
{{ input_text('conf_value', 'manage-setting-field-value', conf_var.value, 'number') }}
{% elseif conf_var.type == 'boolean' %}
{{ input_checkbox('conf_value', 'Enabled', conf_var.value) }}
{% elseif config_type == 'string' %}
{{ input_text('conf_value', 'manage-setting-field-value', config_value, 'text') }}
{% elseif config_type == 'int' %}
{{ input_text('conf_value', 'manage-setting-field-value', config_value, 'number') }}
{% elseif config_type == 'float' %}
{{ input_text('conf_value', 'manage-setting-field-value', config_value, 'number', '', false, {'step':'0.01'}) }}
{% elseif config_type == 'bool' %}
{{ input_checkbox('conf_value', 'Enabled', config_value) }}
{% endif %}
</label>

View file

@ -24,20 +24,20 @@
</tr>
</thead>
<tbody>
{% for var in conf_vars %}
{% for var in config_vars %}
<tr class="manage-list-setting">
<td class="manage-list-setting-key">
<div class="manage-list-setting-key-text">{{ var.key }}</div>
<div class="manage-list-setting-key-text">{{ var.name }}</div>
</td>
<td class="manage-list-setting-type manage-list-setting-type--{{ var.type }}">
<div class="manage-list-setting-type-text">{{ var.type }}</div>
</td>
<td class="manage-list-setting-value">
<div class="manage-list-setting-value-text">{{ var.value }}</div>
<div class="manage-list-setting-value-text">{{ var.name in config_hidden ? '*** hidden ***' : var }}</div>
</td>
<td class="manage-list-setting-options">
<a class="input__button input__button--autosize" href="{{ url('manage-general-setting', {'name': var.key}) }}" title="Edit"><i class="fas fa-pen fa-fw"></i></a>
<a class="input__button input__button--autosize input__button--destroy" href="{{ url('manage-general-setting-delete', {'name': var.key}) }}" title="Delete"><i class="fas fa-times fa-fw"></i></a>
<a class="input__button input__button--autosize" href="{{ url('manage-general-setting', {'name': var.name}) }}" title="Edit"><i class="fas fa-pen fa-fw"></i></a>
<a class="input__button input__button--autosize input__button--destroy" href="{{ url('manage-general-setting-delete', {'name': var.name}) }}" title="Delete"><i class="fas fa-times fa-fw"></i></a>
</td>
</tr>
{% endfor %}