ch1p_io_web/engine/skin.php
2024-01-31 20:45:40 +03:00

242 lines
7.1 KiB
PHP

<?php
require_once 'lib/themes.php';
$SkinState = new class {
public array $lang = [];
public string $title = 'title';
public array $meta = [];
public array $options = [
'full_width' => false,
'wide' => false,
'dynlogo_enabled' => true,
'logo_path_map' => [],
'logo_link_map' => [],
];
public array $static = [];
};
function render($f, ...$vars): void {
global $SkinState;
$f = '\\skin\\'.str_replace('/', '\\', $f);
$ctx = new SkinContext(substr($f, 0, ($pos = strrpos($f, '\\'))));
$body = call_user_func_array([$ctx, substr($f, $pos + 1)], $vars);
if (is_array($body))
list($body, $js) = $body;
else
$js = null;
$theme = getUserTheme();
if ($theme != 'auto' && !themeExists($theme))
$theme = 'auto';
$layout_ctx = new SkinContext('\\skin\\base');
$lang = [];
foreach ($SkinState->lang as $key)
$lang[$key] = lang($key);
$lang = !empty($lang) ? json_encode($lang, JSON_UNESCAPED_UNICODE) : '';
$html = $layout_ctx->layout(
static: $SkinState->static,
theme: $theme,
title: $SkinState->title,
opts: $SkinState->options,
js: $js,
meta: $SkinState->meta,
unsafe_lang: $lang,
unsafe_body: $body,
exec_time: exectime()
);
echo $html;
exit;
}
function set_title(string $title): void {
global $SkinState;
if (str_starts_with($title, '$'))
$title = lang(substr($title, 1));
else if (str_starts_with($title, '\\$'))
$title = substr($title, 1);
$SkinState->title = $title;
}
function set_skin_opts(array $options) {
global $SkinState;
$SkinState->options = array_merge($SkinState->options, $options);
}
function add_skin_strings(array $keys): void {
global $SkinState;
$SkinState->lang = array_merge($SkinState->lang, $keys);
}
function add_skin_strings_re(string $re): void {
global $__lang;
add_skin_strings($__lang->search($re));
}
function add_static(string ...$files): void {
global $SkinState;
foreach ($files as $file)
$SkinState->static[] = $file;
}
function add_meta(array ...$data) {
global $SkinState;
$SkinState->meta = array_merge($SkinState->meta, $data);
}
class SkinContext {
protected string $ns;
protected array $data = [];
function __construct(string $namespace) {
$this->ns = $namespace;
require_once APP_ROOT.str_replace('\\', DIRECTORY_SEPARATOR, $namespace).'.skin.php';
}
function __call($name, array $arguments) {
$plain_args = array_is_list($arguments);
$fn = $this->ns.'\\'.$name;
$refl = new ReflectionFunction($fn);
$fparams = $refl->getParameters();
assert(count($fparams) == count($arguments) + 1, "$fn: invalid number of arguments (".count($fparams)." != ".(count($arguments) + 1).")");
foreach ($fparams as $n => $param) {
if ($n == 0)
continue; // skip $ctx
$key = $plain_args ? $n - 1 : $param->name;
if (!$plain_args && !array_key_exists($param->name, $arguments)) {
if (!$param->isDefaultValueAvailable())
throw new InvalidArgumentException('argument '.$param->name.' not found');
else
continue;
}
if (is_string($arguments[$key]) || $arguments[$key] instanceof SkinString) {
if (is_string($arguments[$key]))
$arguments[$key] = new SkinString($arguments[$key]);
if (($pos = strpos($param->name, '_')) !== false) {
$mod_type = match (substr($param->name, 0, $pos)) {
'unsafe' => SkinStringModificationType::RAW,
'urlencoded' => SkinStringModificationType::URL,
'jsonencoded' => SkinStringModificationType::JSON,
'addslashes' => SkinStringModificationType::ADDSLASHES,
default => SkinStringModificationType::HTML
};
} else {
$mod_type = SkinStringModificationType::HTML;
}
$arguments[$key]->setModType($mod_type);
}
}
array_unshift($arguments, $this);
return call_user_func_array($fn, $arguments);
}
function &__get(string $name) {
$fn = $this->ns.'\\'.$name;
if (function_exists($fn)) {
$f = [$this, $name];
return $f;
}
if (array_key_exists($name, $this->data))
return $this->data[$name];
}
function __set(string $name, $value) {
$this->data[$name] = $value;
}
function if_not($cond, $callback, ...$args) {
return $this->_if_condition(!$cond, $callback, ...$args);
}
function if_true($cond, $callback, ...$args) {
return $this->_if_condition($cond, $callback, ...$args);
}
function if_admin($callback, ...$args) {
return $this->_if_condition(is_admin(), $callback, ...$args);
}
function if_dev($callback, ...$args) {
return $this->_if_condition(is_dev(), $callback, ...$args);
}
function if_then_else($cond, $cb1, $cb2) {
return $cond ? $this->_return_callback($cb1) : $this->_return_callback($cb2);
}
function csrf($key): string {
return csrf_get($key);
}
protected function _if_condition($condition, $callback, ...$args) {
if (is_string($condition) || $condition instanceof Stringable)
$condition = (string)$condition !== '';
if ($condition)
return $this->_return_callback($callback, $args);
return '';
}
protected function _return_callback($callback, $args = []) {
if (is_callable($callback))
return call_user_func_array($callback, $args);
else if (is_string($callback))
return $callback;
}
function for_each(array $iterable, callable $callback) {
$html = '';
foreach ($iterable as $k => $v)
$html .= call_user_func($callback, $v, $k);
return $html;
}
function lang(...$args): string {
return htmlescape($this->langRaw(...$args));
}
function langRaw(string $key, ...$args) {
$val = lang($key);
return empty($args) ? $val : sprintf($val, ...$args);
}
}
enum SkinStringModificationType {
case RAW;
case URL;
case HTML;
case JSON;
case ADDSLASHES;
}
class SkinString implements Stringable {
protected SkinStringModificationType $modType;
function __construct(protected string $string) {}
function setModType(SkinStringModificationType $modType) { $this->modType = $modType; }
function __toString(): string {
return match ($this->modType) {
SkinStringModificationType::HTML => htmlescape($this->string),
SkinStringModificationType::URL => urlencode($this->string),
SkinStringModificationType::JSON => json_encode($this->string, JSON_UNESCAPED_UNICODE),
SkinStringModificationType::ADDSLASHES => addslashes($this->string),
default => $this->string,
};
}
}