summaryrefslogtreecommitdiff
path: root/engine/skin.php
diff options
context:
space:
mode:
authorEvgeny Zinoviev <me@ch1p.io>2024-01-31 06:11:00 +0300
committerEvgeny Zinoviev <me@ch1p.io>2024-01-31 20:45:40 +0300
commitc0dc531ebefd8912819f3b6c8bda1fed3c7e750c (patch)
tree2c75aa9df182260aef09faf4befd81a4c2b9c5e2 /engine/skin.php
parent48d688cdf7f9eae1bf11b8a6f0e5b98687c604cb (diff)
make it simple, but not simpler
Diffstat (limited to 'engine/skin.php')
-rw-r--r--engine/skin.php241
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,
+ };
+ }
+}