Template/Update (Part 44)

* convert admin - config
 * test for presence of Memcached on enable
 * allow self-signed certs on domain self test
This commit is contained in:
Sarjuuk
2025-08-22 16:01:59 +02:00
parent f1b613cfa0
commit 3f8a1838c0
9 changed files with 260 additions and 17 deletions

View File

@@ -0,0 +1,113 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminSiteconfigResponse extends TemplateResponse
{
protected int $requiredUserGroup = U_GROUP_ADMIN | U_GROUP_DEV;
protected string $template = 'admin/siteconfig';
protected string $pageName = 'siteconfig';
protected ?int $activeTab = parent::TAB_STAFF;
protected array $breadcrumb = [4, 2, 18]; // Staff > Development > Site Configuration
protected function generate() : void
{
$this->h1 = 'Site Configuration';
array_unshift($this->title, $this->h1);
$this->lvTabs = new Tabs(['parent' => "\$\$WH.ge('tabs-generic')"]);
parent::generate();
$this->addScript([SC_CSS_STRING, <<<CSS
.grid input[type='text'], .grid input[type='number'] { width:250px; text-align:left; }
.grid input[type='button'] { width:65px; padding:2px; }
.grid a.tip { margin:0px 5px; opacity:0.8; }
.grid a.tip:hover { opacity:1; }
.grid tr { height:30px; }
.grid .disabled { opacity:0.4 !important; }
.grid .status { position:absolute; right:5px; }
CSS]);
$head = '<tr><th><b>Key</b></th><th><b>Value</b></th><th style="width:150px;"><b>Options</b></th></tr>';
foreach (Cfg::$categories as $idx => $catName)
{
$rows = '';
foreach (Cfg::forCategory($idx) as $key => [$value, $flags, , $default, $comment])
$rows .= $this->buildRow($key, $value, $flags, $default, $comment);
if ($idx == Cfg::CAT_MISCELLANEOUS)
$rows .= '<tr><td colspan="3"><a class="icon-add" onclick="cfg_add(this)">new configuration</a></td></tr>';
if (!$rows)
continue;
$this->lvTabs->addDataTab(Profiler::urlize($catName), $catName, '<table class="grid">' . $head . $rows . '</table>');
}
}
private function buildRow(string $key, string $value, int $flags, ?string $default, string $comment) : string
{
$buff = '<tr>';
$info = explode(' - ', $comment);
$key = $flags & Cfg::FLAG_PHP ? strtolower($key) : strtoupper($key);
// name
if (!empty($info[0]))
$buff .= '<td>'.sprintf(Util::$dfnString, $info[0], $key).'</td>';
else
$buff .= '<td>'.$key.'</td>';
// value
if ($flags & Cfg::FLAG_TYPE_BOOL)
$buff .= '<td><div id="'.$key.'"><input id="'.$key.'1" type="radio" name="'.$key.'" value="1" '.($value ? 'checked' : null).' /><label for="'.$key.'1">Enabled</label> <input id="'.$key.'0" type="radio" name="'.$key.'" value="0" '.($value ? null : 'checked').' /><label for="'.$key.'0">Disabled</label></div></td>';
else if ($flags & Cfg::FLAG_OPT_LIST && !empty($info[1]))
{
$buff .= '<td><select id="'.$key.'" name="'.$key.'">';
foreach (explode(', ', $info[1]) as $option)
{
[$idx, $name] = explode(':', $option);
$buff .= '<option value="'.$idx.'"'.($value == $idx ? ' selected ' : null).'>'.$name.'</option>';
}
$buff .= '</select></td>';
}
else if ($flags & Cfg::FLAG_BITMASK && !empty($info[1]))
{
$buff .= '<td><div id="'.$key.'">';
foreach (explode(', ', $info[1]) as $option)
{
[$idx, $name] = explode(':', $option);
$buff .= '<input id="'.$key.$idx.'" type="checkbox" name="'.$key.'" value="'.$idx.'"'.($value & (1 << $idx) ? ' checked ' : null).'><label for="'.$key.$idx.'">'.$name.'</label>';
}
$buff .= '</div></td>';
}
else
$buff .= '<td><input id="'.$key.'" type="'.($flags & Cfg::FLAG_TYPE_STRING ? 'text" placeholder="<empty>' : 'number'.($flags & Cfg::FLAG_TYPE_FLOAT ? '" step="any' : '')).'" name="'.$key.'" value="'.$value.'" /></td>';
// actions
$buff .= '<td style="position:relative;">';
$buff .= '<a class="icon-save tip" onclick="cfg_submit.bind(this, \''.$key.'\')()" onmouseover="$WH.Tooltip.showAtCursor(event, \'Save Changes\', 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"></a>';
if ($default)
$buff .= '|<a class="icon-refresh tip" onclick="cfg_default(\''.$key.'\', \''.$default.'\')" onmouseover="$WH.Tooltip.showAtCursor(event, \'Restore Default Value\', 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"></a>';
else
$buff .= '|<a class="icon-refresh tip disabled"></a>';
if (!($flags & Cfg::FLAG_PERSISTENT))
$buff .= '|<a class="icon-delete tip" onclick="cfg_remove.bind(this, \''.$key.'\')()" onmouseover="$WH.Tooltip.showAtCursor(event, \'Remove Setting\', 0, 0, \'q\')" onmousemove="$WH.Tooltip.cursorUpdate(event)" onmouseout="$WH.Tooltip.hide()"></a>';
$buff .= '<span class="status"></span></td></tr>';
return $buff;
}
}
?>

View File

@@ -0,0 +1,34 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminSiteconfigActionAddResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_DEV | U_GROUP_ADMIN;
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => Cfg::PATTERN_CONF_KEY_FULL]],
'val' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ]
);
protected function generate() : void
{
if (!$this->assertGET('key', 'val'))
{
trigger_error('AdminSiteconfigActionAddResponse - malformed request received', E_USER_ERROR);
$this->result = Lang::main('intError');
return;
}
$key = trim($this->_get['key']);
$val = trim(urldecode($this->_get['val']));
$this->result = Cfg::add($key, $val);
}
}
?>

View File

@@ -0,0 +1,30 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminSiteconfigActionRemoveResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_DEV | U_GROUP_ADMIN;
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => Cfg::PATTERN_CONF_KEY_FULL]]
);
protected function generate() : void
{
if (!$this->assertGET('key'))
{
trigger_error('AdminSiteconfigActionRemoveResponse - malformed request received', E_USER_ERROR);
$this->result = Lang::main('intError');
return;
}
$this->result = Cfg::delete($this->_get['key']);
}
}
?>

View File

@@ -0,0 +1,34 @@
<?php
namespace Aowow;
if (!defined('AOWOW_REVISION'))
die('illegal access');
class AdminSiteconfigActionUpdateResponse extends TextResponse
{
protected int $requiredUserGroup = U_GROUP_DEV | U_GROUP_ADMIN;
protected array $expectedGET = array(
'key' => ['filter' => FILTER_VALIDATE_REGEXP, 'options' => ['regexp' => Cfg::PATTERN_CONF_KEY_FULL]],
'val' => ['filter' => FILTER_CALLBACK, 'options' => [self::class, 'checkTextBlob'] ]
);
protected function generate() : void
{
if (!$this->assertGET('key', 'val'))
{
trigger_error('AdminSiteconfigActionUpdateResponse - malformed request received', E_USER_ERROR);
$this->result = Lang::main('intError');
return;
}
$key = trim($this->_get['key']);
$val = trim(urldecode($this->_get['val']));
$this->result = Cfg::set($key, $val);
}
}
?>

View File

@@ -7,8 +7,8 @@ if (!defined('AOWOW_REVISION'))
class Cfg class Cfg
{ {
public const PATTERN_CONF_KEY = '/[a-z0-9_\.\-]/i'; public const PATTERN_CONF_KEY_CHAR = '/[a-z0-9_\.\-]/i';
public const PATTERN_INV_CONF_KEY = '/[^a-z0-9_\.\-]/i'; public const PATTERN_CONF_KEY_FULL = '/^[a-z0-9_\.\-]+$/i';
public const PATTERN_INVALID_CHARS = '/\p{C}/ui'; public const PATTERN_INVALID_CHARS = '/\p{C}/ui';
// config flags // config flags
@@ -116,7 +116,7 @@ class Cfg
$key = strtolower($key); $key = strtolower($key);
if (preg_match(self::PATTERN_INV_CONF_KEY, $key)) if (!preg_match(self::PATTERN_CONF_KEY_FULL, $key))
return 'invalid chars in option name: [a-z 0-9 _ . -] are allowed'; return 'invalid chars in option name: [a-z 0-9 _ . -] are allowed';
if (isset(self::$store[$key])) if (isset(self::$store[$key]))
@@ -129,7 +129,7 @@ class Cfg
return 'this configuration option cannot be set'; return 'this configuration option cannot be set';
$flags = self::FLAG_TYPE_STRING | self::FLAG_PHP; $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)) if (!is_int(DB::Aowow()->query('INSERT IGNORE INTO ?_config (`key`, `value`, `cat`, `flags`) VALUES (?, ?, ?d, ?d)', $key, $value, self::CAT_MISCELLANEOUS, $flags)))
return 'internal error'; return 'internal error';
self::$store[$key] = [$value, $flags, self::CAT_MISCELLANEOUS, null, null]; self::$store[$key] = [$value, $flags, self::CAT_MISCELLANEOUS, null, null];
@@ -349,7 +349,7 @@ class Cfg
} }
if ($flags & self::FLAG_TYPE_BOOL) if ($flags & self::FLAG_TYPE_BOOL)
$value = (bool)$value; $value = $value ? 1 : 0;
return ''; return '';
} }
@@ -384,7 +384,17 @@ class Cfg
trigger_error($msg, E_USER_ERROR); trigger_error($msg, E_USER_ERROR);
} }
private static function locales(/*int|string*/ $value, ?string &$msg = '') : bool private static function useSSL() : bool
{
return (($_SERVER['HTTPS'] ?? 'off') != 'off') || (self::$store['force_ssl'][self::IDX_VALUE] ?? 0);
}
/***************************/
/* onSet/onLoad validators */
/***************************/
private static function locales(int|string $value, ?string &$msg = '') : bool
{ {
if (!CLI) if (!CLI)
return true; return true;
@@ -397,7 +407,7 @@ class Cfg
return false; return false;
} }
private static function acc_auth_mode(/*int|string*/ $value, ?string &$msg = '') : bool private static function acc_auth_mode(int|string $value, ?string &$msg = '') : bool
{ {
if ($value == 1 && !extension_loaded('gmp')) if ($value == 1 && !extension_loaded('gmp'))
{ {
@@ -408,7 +418,7 @@ class Cfg
return true; return true;
} }
private static function profiler_enable(/*int|string*/ $value, ?string &$msg = '') : bool private static function profiler_enable(int|string $value, ?string &$msg = '') : bool
{ {
if ($value != 1) if ($value != 1)
return true; return true;
@@ -416,7 +426,7 @@ class Cfg
return Profiler::queueStart($msg); return Profiler::queueStart($msg);
} }
private static function static_host(/*int|string*/ $value, ?string &$msg = '') : bool private static function static_host(int|string $value, ?string &$msg = '') : bool
{ {
self::$store['static_url'] = array( // points js to images & scripts self::$store['static_url'] = array( // points js to images & scripts
(self::useSSL() ? 'https://' : 'http://').$value, (self::useSSL() ? 'https://' : 'http://').$value,
@@ -429,7 +439,7 @@ class Cfg
return true; return true;
} }
private static function site_host(/*int|string*/ $value, ?string &$msg = '') : bool private static function site_host(int|string $value, ?string &$msg = '') : bool
{ {
self::$store['host_url'] = array( // points js to executable files self::$store['host_url'] = array( // points js to executable files
(self::useSSL() ? 'https://' : 'http://').$value, (self::useSSL() ? 'https://' : 'http://').$value,
@@ -442,9 +452,15 @@ class Cfg
return true; return true;
} }
private static function useSSL() : bool private static function cache_mode(int|string $value, ?string &$msg = '') : bool
{ {
return (($_SERVER['HTTPS'] ?? 'off') != 'off') || (self::$store['force_ssl'][self::IDX_VALUE] ?? 0); if ($value & 0x2 && !class_exists('\Memcached'))
{
$msg .= 'PHP extension Memcached is not enabled.';
return false;
}
return true;
} }
private static function screenshot_min_size(int|string $value, ?string &$msg = '') : bool private static function screenshot_min_size(int|string $value, ?string &$msg = '') : bool

View File

@@ -217,6 +217,12 @@ trait TrCache
private function memcached() : ?\Memcached private function memcached() : ?\Memcached
{ {
if (!class_exists('\Memcached'))
{
trigger_error('Memcached is enabled by us but not in php!', E_USER_ERROR);
return null;
}
if (!$this->memcached && (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED)) if (!$this->memcached && (Cfg::get('CACHE_MODE') & CACHE_MODE_MEMCACHED))
{ {
$this->memcached = new \Memcached(); $this->memcached = new \Memcached();

View File

@@ -114,7 +114,7 @@ CLISetup::registerUtility(new class extends UtilityScript
CLI::write(); CLI::write();
} }
if (CLI::read(['idx' => ['', false, false, Cfg::PATTERN_CONF_KEY]], $uiIndex) && $uiIndex && $uiIndex['idx'] !== '') if (CLI::read(['idx' => ['', false, false, Cfg::PATTERN_CONF_KEY_CHAR]], $uiIndex) && $uiIndex && $uiIndex['idx'] !== '')
{ {
$idx = array_search(strtolower($uiIndex['idx']), $cfgList); $idx = array_search(strtolower($uiIndex['idx']), $cfgList);
if ($idx === false) if ($idx === false)
@@ -147,7 +147,7 @@ CLISetup::registerUtility(new class extends UtilityScript
CLI::write(); CLI::write();
$setting = array( $setting = array(
'key' => ['option name', false, false, Cfg::PATTERN_CONF_KEY], 'key' => ['option name', false, false, Cfg::PATTERN_CONF_KEY_CHAR],
'val' => ['value'] 'val' => ['value']
); );
if (CLI::read($setting, $uiSetting) && $uiSetting) if (CLI::read($setting, $uiSetting) && $uiSetting)
@@ -443,7 +443,13 @@ CLISetup::registerUtility(new class extends UtilityScript
private function testCase(&$protocol, &$host, $testFile, &$status) : bool private function testCase(&$protocol, &$host, $testFile, &$status) : bool
{ {
$res = get_headers($protocol.$host.$testFile, true); // https://stackoverflow.com/questions/14279095/allow-self-signed-certificates-for-https-wrapper
$ctx = stream_context_create(array(
'ssl' => ['verify_peer' => false,
'allow_self_signed' => true]
));
$res = get_headers($protocol.$host.$testFile, true, $ctx);
if (!preg_match('/HTTP\/[0-9\.]+\s+([0-9]+)/', $res[0], $m)) if (!preg_match('/HTTP\/[0-9\.]+\s+([0-9]+)/', $res[0], $m))
return false; return false;

View File

@@ -0,0 +1,2 @@
-- set on_set_fn check
UPDATE `aowow_config` SET `flags` = `flags` | 1024 WHERE `key` = 'cache_mode';

View File

@@ -1,6 +1,8 @@
<?php namespace Aowow; ?> <?php
namespace Aowow\Template;
<?php $this->brick('header'); ?> $this->brick('header');
?>
<script type="text/javascript"> <script type="text/javascript">
function createStatusIcon(errTxt) function createStatusIcon(errTxt)