diff options
Diffstat (limited to 'engine/skin.php')
-rw-r--r-- | engine/skin.php | 241 |
1 files changed, 241 insertions, 0 deletions
diff --git a/engine/skin.php b/engine/skin.php new file mode 100644 index 0000000..6150668 --- /dev/null +++ b/engine/skin.php @@ -0,0 +1,241 @@ +<?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, + }; + } +} |