diff options
author | Freya Murphy <freya@freyacat.org> | 2024-12-23 10:39:16 -0500 |
---|---|---|
committer | Freya Murphy <freya@freyacat.org> | 2024-12-23 10:39:16 -0500 |
commit | de9cae795f93d03e68d965c59af4b21d96df4ec7 (patch) | |
tree | ad4f903c04630b3b92e2b9b5d06d5b8647d299bb /src/lib | |
parent | license (diff) | |
download | crimson-de9cae795f93d03e68d965c59af4b21d96df4ec7.tar.gz crimson-de9cae795f93d03e68d965c59af4b21d96df4ec7.tar.bz2 crimson-de9cae795f93d03e68d965c59af4b21d96df4ec7.zip |
initial
Diffstat (limited to 'src/lib')
-rw-r--r-- | src/lib/database.php | 337 | ||||
-rw-r--r-- | src/lib/error.php | 218 | ||||
-rw-r--r-- | src/lib/hooks.php | 178 | ||||
-rw-r--r-- | src/lib/html.php | 95 | ||||
-rw-r--r-- | src/lib/lang.php | 186 | ||||
-rw-r--r-- | src/lib/meta.php | 28 |
6 files changed, 1042 insertions, 0 deletions
diff --git a/src/lib/database.php b/src/lib/database.php new file mode 100644 index 0000000..416ef8f --- /dev/null +++ b/src/lib/database.php @@ -0,0 +1,337 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +function __nullify(mixed $val): mixed +{ + if (!$val) { + return NULL; + } else { + return $val; + } +} + +class DatabaseQuery { + + private \PDO $conn; + private string $query; + + private bool $where; + private bool $set; + + private array $param; + + function __construct(\PDO $conn) + { + $this->conn = $conn; + $this->query = ''; + + $this->set = FALSE; + $this->where = FALSE; + $this->param = array(); + } + + /// + /// ARBITRARY QUERY + /// + + public function query(string $query): DatabaseQuery + { + $this->query .= $query; + return $this; + } + + /// + /// SELECT + /// + + public function select(string $select): DatabaseQuery + { + $this->query .= "SELECT $select\n"; + return $this; + } + + public function from(string $from): DatabaseQuery + { + $this->query .= "FROM $from\n"; + return $this; + } + + /// + /// INSERT + /// + + public function insert_into(string $insert, string ...$columns): DatabaseQuery + { + $this->query .= "INSERT INTO $insert\n ("; + foreach ($columns as $idx => $column) { + if ($idx !== 0) { + $this->query .= ","; + } + $this->query .= $column; + } + $this->query .= ")\n"; + return $this; + } + + public function values(mixed ...$values): DatabaseQuery + { + $this->query .= "VALUES ("; + foreach ($values as $idx => $value) { + if ($idx !== 0) { + $this->query .= ","; + } + $this->query .= "?"; + array_push($this->param, $value); + } + $this->query .= ")\n"; + return $this; + } + + /// + /// WHERE + /// + + public function where(string $cond): DatabaseQuery + { + if (!$this->where) { + $this->where = TRUE; + $this->query .= "WHERE "; + } else { + $this->query .= "AND "; + } + $this->query .= "$cond "; + return $this; + } + + /** + * @param array<mixed> $array + */ + public function where_in(string $column, array $array): DatabaseQuery + { + if (!$this->where) { + $this->where = TRUE; + $this->query .= "WHERE "; + } else { + $this->query .= "AND "; + } + if (empty($array)) { + $this->query .= "FALSE\n"; + return $this; + } + $in = $this->in($array); + $this->query .= "$column $in\n"; + return $this; + } + + private function in(array $array): string + { + $in = 'IN ('; + foreach ($array as $idx => $item) { + if ($idx != 0) { + $in .= ","; + } + $in .= "?"; + array_push($this->param, $item); + } + $in .= ")"; + return $in; + } + + /// + /// OPERATORS + /// + + public function like(mixed $item): DatabaseQuery + { + $this->query .= "LIKE ?\n"; + array_push($this->param, $item); + return $this; + } + + public function eq(mixed $item): DatabaseQuery + { + $this->query .= "= ?\n"; + array_push($this->param, $item); + return $this; + } + + public function ne(mixed $item): DatabaseQuery + { + $this->query .= "<> ?\n"; + array_push($this->param, $item); + return $this; + } + + public function lt(mixed $item): DatabaseQuery + { + $this->query .= "< ?\n"; + array_push($this->param, $item); + return $this; + } + + public function le(mixed $item): DatabaseQuery + { + $this->query .= "<= ?\n"; + array_push($this->param, $item); + return $this; + } + + /// + /// JOINS + /// + + public function join(string $table, string $on, string $type = 'LEFT'): DatabaseQuery + { + $this->query .= "$type JOIN $table ON $on\n"; + return $this; + } + + /// + /// LIMIT, OFFSET, ORDER + /// + + public function limit(int $limit): DatabaseQuery + { + $this->query .= "LIMIT ?\n"; + array_push($this->param, $limit); + return $this; + } + + public function offset(int $offset): DatabaseQuery + { + $this->query .= "OFFSET ?\n"; + array_push($this->param, $offset); + return $this; + } + + public function order_by(string $column, string $order = 'ASC'): DatabaseQuery + { + $this->query .= "ORDER BY " . $column . ' ' . $order . ' '; + return $this; + } + + /// + /// COLLECT + /// + + public function rows(mixed ...$params): array + { + $args = $this->param; + foreach ($params as $param) { + array_push($args, $param); + } + $stmt = $this->conn->prepare($this->query); + try { + $stmt->execute($args); + } catch (Exception $ex) { + echo $ex; + echo '<br> >> caused by <<<br>'; + echo str_replace("\n", "<br>", $this->query); + } + return __nullify($stmt->fetchAll(PDO::FETCH_ASSOC)) ?? []; + } + + public function row(mixed ...$params): ?array + { + $args = $this->param; + foreach ($params as $param) { + array_push($args, $param); + } + $stmt = $this->conn->prepare($this->query); + $stmt->execute($args); + return __nullify($stmt->fetch(PDO::FETCH_ASSOC)); + } + + public function execute(mixed ...$params): bool + { + $args = $this->param; + foreach ($params as $param) { + array_push($args, $param); + } + $stmt = $this->conn->prepare($this->query); + try { + $stmt->execute($args); + return TRUE; + } catch (Exception $_e) { + echo $_e; + echo '<br> >> caused by <<<br>'; + echo str_replace("\n", "<br>", $this->query); + return FALSE; + } + } +} + +/** + * DatabaseHelper + * allows queries on the + * postgres database + */ +class DatabaseHelper { + + private ?\PDO $conn; + + function __construct() + { + $this->conn = NULL; + } + + private function connect(): \PDO + { + if ($this->conn === NULL) { + $user = getenv("POSTGRES_USER"); + $pass = getenv("POSTGRES_PASSWORD"); + $db = getenv("POSTGRES_DB"); + $host = 'postgres'; + $port = '5432'; + + $conn_str = sprintf("pgsql:host=%s;port=%d;dbname=%s;user=%s;password=%s", + $host, + $port, + $db, + $user, + $pass + ); + $this->conn = new \PDO($conn_str); + $this->conn->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + } + return $this->conn; + } + + public function select(string $select): DatabaseQuery + { + $conn = $this->connect(); + $query = new DatabaseQuery($conn); + return $query->select($select); + } + + public function insert_into(string $insert, string ...$columns): DatabaseQuery + { + $conn = $this->connect(); + $query = new DatabaseQuery($conn); + return $query->insert_into($insert, ...$columns); + } + + public function query(string $query_str): DatabaseQuery + { + $conn = $this->connect(); + $query = new DatabaseQuery($conn); + return $query->query($query_str); + } +} + diff --git a/src/lib/error.php b/src/lib/error.php new file mode 100644 index 0000000..ae772d2 --- /dev/null +++ b/src/lib/error.php @@ -0,0 +1,218 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +/** + * CRIMSON ERROR FUNCTIONS + * + * Insead of using trigger_error, crimson has its own CRIMSON_<type> error + * functions. It is prefered to use this they hook more nicely into crimson. + * trigger_error is still okay to use since crimson creates its own error + * handler, and will catch it anyways. + * + * WARNING: DO NOT create your own error handler since this MAY interfere with + * crimson's control flow. + */ + +// crimson's fatal error handler will set $errno to CRIMSON_E_FATAL_ERROR. +define('CRIMSON_E_FATAL_ERROR', 0); + +function __CRIMSON_error_pretty_print_name(int $errno) { + switch ($errno) { + case CRIMSON_E_FATAL_ERROR: // 0 // + return 'Fatal Error'; + + case E_ERROR: // 1 // + case E_CORE_ERROR: // 16 // + case E_USER_ERROR: // 256 // + case E_RECOVERABLE_ERROR: // 4096 // + return 'Error'; + + case E_WARNING: // 2 // + case E_CORE_WARNING: // 32 // + case E_USER_WARNING: // 512 // + return 'Warning'; + + case E_PARSE: // 4 // + return 'Parse Error'; + + case E_NOTICE: // 8 // + case E_USER_NOTICE: // 1024 // + return 'Notice'; + + case E_COMPILE_ERROR: // 128 // + return 'Compile Error'; + + case E_DEPRECATED: // 8192 // + case E_USER_DEPRECATED: // 16384 // + return 'Deprecated'; + + default: + return 'Unknown Error'; + } +} + +function __CRIMSON_error_pretty_print_bg_color(int $errno) { + switch ($errno) { + case CRIMSON_E_FATAL_ERROR: // 0 // + case E_ERROR: // 1 // + case E_PARSE: // 4 // + case E_CORE_ERROR: // 16 // + case E_COMPILE_ERROR: // 128 // + case E_USER_ERROR: // 256 // + case E_RECOVERABLE_ERROR: // 4096 // + return '#dc143c'; + + case E_WARNING: // 2 // + case E_CORE_WARNING: // 32 // + case E_USER_WARNING: // 512 // + case E_DEPRECATED: // 8192 // + case E_USER_DEPRECATED: // 16384 // + return '#db6d13'; + + case E_NOTICE: // 8 // + case E_USER_NOTICE: // 1024 // + default: + return '#13a6db'; + } +} + +function __CRIMSON_error_pretty_print_text_color(int $errno) { + switch ($errno) { + case CRIMSON_E_FATAL_ERROR: // 0 // + case E_ERROR: // 1 // + case E_PARSE: // 4 // + case E_CORE_ERROR: // 16 // + case E_COMPILE_ERROR: // 128 // + case E_USER_ERROR: // 256 // + case E_RECOVERABLE_ERROR: // 4096 // + return '#fff'; + + case E_WARNING: // 2 // + case E_CORE_WARNING: // 32 // + case E_USER_WARNING: // 512 // + case E_DEPRECATED: // 8192 // + case E_USER_DEPRECATED: // 16384 // + return '#fff'; + + case E_NOTICE: // 8 // + case E_USER_NOTICE: // 1024 // + default: + return '#181818'; + } +} + +/** + * __CRIMSON_error_pretty_print + * + * Prints a pretty HTML error message using the same set of parameters + * from PHP's error handler. + * + * Unless CRIMSON detects that you are using a tty, crimson will opt to + * pretty print all errors. + */ +function __CRIMSON_error_pretty_print( + int $errno, + string $errstr, + ?string $errfile = NULL, + ?int $errline = NULL, + ?array $errcontext = NULL, +): void { + + $name = __CRIMSON_error_pretty_print_name($errno); + $bg = __CRIMSON_error_pretty_print_bg_color($errno); + $text = __CRIMSON_error_pretty_print_text_color($errno); + + $root_style = " + all: unset !important; + display: block !important; + margin: .1em !important; + background: {$bg} !important; + color: {$text} !important; + font-family: Helvetica, Verdana, Courier, monospace !important; + font-size: 14px !important;"; + + $div_style = " + padding: .5em; !important;"; + + $span_style = " + display: block !important"; + + $title_style=" + font-size: 1.2em !important; + font-weight: bold !important; + background: rgba(255,255,255,.2); !important"; + + $html = <<<EOF +<div style="{$root_style}"> + <div style="{$div_style}\n{$title_style}"> + (!) {$name} + </div> + <div style="{$div_style}"> + <span style="{$span_style}"> + In file {$errfile}:{$errline} + </span> + <span style="{$span_style}"> + >>> {$errstr} + </span> + </div> +</div> +EOF; + echo $html; +} + +function __CRIMSON_error_print( + int $errno, + string $errstr, + ?string $errfile = NULL, + ?int $errline = NULL, + ?array $errcontext = NULL, +): void { + $name = __CRIMSON_error_pretty_print_name($errno); + echo "{$name}: {$errstr}\n\tIn file {$errfile}:{$errline}\n"; +} + +function CRIMSON_error_handler( + int $errno, + string $errstr, + ?string $errfile = NULL, + ?int $errline = NULL, + ?array $errcontext = NULL, +): void { + __CRIMSON_error_pretty_print($errno, $errstr, $errfile, $errline, $errcontext); +} + +set_error_handler("CRIMSON_error_handler"); + +function CRIMSON_ERROR(string $msg): void { + $frame = debug_backtrace()[1]; + CRIMSON_error_handler(E_ERROR, $msg, + $frame['file'], $frame['line']); +} + +function CRIMSON_FATAL_ERROR(string $msg): void { + $frame = debug_backtrace()[1]; + CRIMSON_error_handler(CRIMSON_E_FATAL_ERROR, $msg, + $frame['file'], $frame['line']); +} + +function CRIMSON_WARNING(string $msg): void { + $frame = debug_backtrace()[1]; + CRIMSON_error_handler(E_WARNING, $msg, + $frame['file'], $frame['line']); +} diff --git a/src/lib/hooks.php b/src/lib/hooks.php new file mode 100644 index 0000000..21854bd --- /dev/null +++ b/src/lib/hooks.php @@ -0,0 +1,178 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +/** + * CRIMSON HOOKS + * + * Crimson supports hooks to allow the user to add functionality inside + * crimson's control flow. This can be used for handling errors, or having + * custom routing functionality. + * + * The current supported hooks are: 'error', 'pre_route', 'init', and 'die'. + * + * To run a hook yourself you can call CRIMSON_HOOK($name, [$args]); + * + * To create a handler, make a function in the global scope called: + * CRIMSON_<name>_hook. i.e. if you are making a init hook handler, make a + * function called CRIMSON_init_hook(...). + * + * If a hook is called and a user hook handeler does not exist, the crimson + * builtin hook will be run instead. + * + * NOTE: It is also allowed for you to create your own hook types, but you must + * also create your own handler for it. If you dont, it will result in a error + * since crimson will not have a builtin hook handler to fall back to. + * + * NOTE: CRIMSON_HOOK does support a third argument called $builtin, but this + * is only to be used by crimson itself, so please dont use it. + * + * WARNING: If a hook cannot find a handler to run, it will call + * CRIMSON_FATAL_ERROR and NEVER return. + */ + +/** + * CRIMSON builtin ERROR hook. + * + * $req - The current request parsed by the Router. All fields are gurenteed + * to be set BESIDES 'uri' and 'uri_str', since parsing of the URI can fail + * and result to a call to this error handler. + * + * This hook is run when any Controller or the Router runs into an issue and + * fails with an HTTP Error. + * + * EXAMPLE: If the Router cannot find the requested Controller and Method + * based in the URI path, it will raise 404 Not Found. + * + * EXAMPLE: If the crimson postgres database is enabled, and db-init either + * failed or is still running (the database is not ready), crimson will raise + * 503 Service Unavaliable. + * + * EXAMPLE: If the provided URI to PHP is unable to be parsed, crimson will + * raise 400 Bad Request. + * + * This is hook is called in ROUTER->handle_error(int code) and + * Controller->error(int code). + * + * NOTE: Unlike CRIMSON's DIE hook, it is supported to re-enter crimson code. + * This error handler must never return, but it is okay to recall crimson to + * try to print an error page. + * + * WARNING: If the user tries to re-enter crimson from this handler and ends + * up raising another error, the user error handler WILL NOT be called again. + * To prevent recursion, a user error handle WILL only ever be called ONCE. + * This is due to that the handler has return type 'never', and is also not + * allowed to recurse. + */ +function CRIMSON_builtin_error_hook(array $req, int $code): never +{ + http_response_code($code); + CRIMSON_DIE("{$code} {$req['uri_str']}"); +} + +/** + * CRIMSON builtin PRE ROUTE hook. + * + * This hook does nothing by default since all required CRIMSON routing is done + * in router.php. + */ +function CRIMSON_builtin_pre_route_hook(Router $router): void +{ +} + +/** + * CRIMSON builtin INIT hook. + * + * This hook does nothing by default since all required CRIMSON init work is + * run in index.php. + * + * This hook can be overridden to run any user required initalization code + * before CRIMSON launches and runs the router. + * + * WARNING: The ROUTER is NOT YET created when this hook is run. Do not call + * ROUTER or ANY CRIMSON code in a user specified init hook. Doing so is + * UNDEFINED BEHAVIOR. + */ +function CRIMSON_builtin_init_hook(): void +{ +} + +/** + * CRIMSON builtin DIE hook. + * + * Calls die with $status, i.e. this is an alias for die(). + */ +function CRIMSON_builtin_die_hook(string|int $status = 0): never +{ + die($status); +} + +/** + * Executes a hook for crimson. + * + * $name - the name of the hook + * $args - the arguments to pass to the hook + * $builtin - force the use of the builtin hook + * + * Looks for CRIMSON_$name_hook first, which is the custom user defined hook. + * If it does not exist, it will run CRIMSON_builtin_$name_hook instead. + */ +function CRIMSON_HOOK( + string $name, + array $args = array(), + bool $builtin = FALSE, +): void { + $names = array(); + if (!$builtin) + $names[] = "CRIMSON_{$name}_hook"; + $names[] = "CRIMSON_builtin_{$name}_hook"; + + // find handler + $handler = NULL; + foreach ($names as $name) { + try { + $handler = new ReflectionFunction($name); + if ($handler) + break; + } catch (Exception $_e) {}; + } + + // error in invalid hook + if (!$handler) { + CRIMSON_ERROR("Invalid hook: {$name}"); + return; + } + + // i dont care to argument check, + // if someone screws up a hook we have bigger problems + $handler->invoke(...$args); +} + +/** + * Executes die() in php. But allows the user to add a hook to handle any + * loose resources before php kills itself. + * + * NOTE: A DIE hook should NEVER be handeled and return. A user provided hook + * must also have a return type of never and immediately die. Do NOT + * call any crimson code such as rerunning the ROUTER. Doing so will result in + * UNDEFINED BEHAVIOR and nasal demons showing up at your doorstep!!! + */ +function CRIMSON_DIE(string|int $status = 0): never +{ + CRIMSON_HOOK('die', [$status]); +} diff --git a/src/lib/html.php b/src/lib/html.php new file mode 100644 index 0000000..fac7a94 --- /dev/null +++ b/src/lib/html.php @@ -0,0 +1,95 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +function esc(string $data, bool $string_esc = FALSE): string { + $flags = ENT_SUBSTITUTE | ENT_HTML401; + if ($string_esc) + $flags |= ENT_QUOTES; + return htmlspecialchars($data, $flags); +} + +function status_code_msg(int $code): ?string { + + static $status_code = array( + 100 => "Continue", + 101 => "Switching Protocols", + 200 => "OK", + 201 => "Created", + 202 => "Accepted", + 203 => "Non-Authoritative Information", + 204 => "No Content", + 205 => "Reset Content", + 206 => "Partial Content", + 300 => "Multiple Choices", + 301 => "Moved Permanently", + 302 => "Found", + 303 => "See Other", + 304 => "Not Modified", + //305 => "Use Proxy", + //306 => "unused", + 307 => "Temporary Redirect", + 308 => "Permanent Redirect", + 400 => "Bad Request", + 401 => "Unauthorized", + 402 => "Payment Required", + 403 => "Forbidden", + 404 => "Not Found", + 405 => "Method Not Allowed", + 406 => "Not Acceptable", + 407 => "Proxy Authentication Required", + 408 => "Request Timeout", + 409 => "Conflict", + 410 => "Gone", + 411 => "Length Required", + 412 => "Precondition Failed", + 413 => "Content Too Large", + 414 => "URI Too Long", + 415 => "Unsupported Media Type", + 416 => "Range Not Satisfiable", + 417 => "Expectation Failed", + 418 => "I'm a teapot", + 421 => "Misdirected Request", + 422 => "Unprocessable Content", + 423 => "Locked", + 424 => "Failed Dependency", + 425 => "Too Early", + 426 => "Upgrade Required", + 428 => "Precondition Required", + 429 => "Too Many Requests", + 431 => "Request Header Fields Too Large", + 451 => "Unavailable For Legal Reasons", + 500 => "Internal Server Error", + 501 => "Not Implemented", + 502 => "Bad Gateway", + 503 => "Service Unavailable", + 504 => "Gateway Timeout", + 505 => "HTTP Version Not Supported", + 506 => "Variant Also Negotiates", + 507 => "Insufficient Storage", + 508 => "Loop Detected", + 510 => "Not Extended", + 511 => "Network Authentication Required", + ); + + return $status_code[$code] ?? NULL; +} + +function is_valid_status_code(int $code): bool { + return is_string(status_code_msg($code)); +} diff --git a/src/lib/lang.php b/src/lib/lang.php new file mode 100644 index 0000000..84a4215 --- /dev/null +++ b/src/lib/lang.php @@ -0,0 +1,186 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +/** + * Returns the lang string for the provided $key + * + * $key - The key of the lang string + * $default - The string to use if lang string $key is undefined + * $sub - List of values to substitute using php's sprinf + */ +function lang( + string $key, + ?string $default = NULL, + ?array $sub = NULL +): string { + $lang = ROUTER->get_lang(); + $result = NULL; + + // lookup lang key + if (isset($lang[$key])) + $result = $lang[$key]; + + // replace with $default if undefined + if ($result === NULL && $default !== NULL) + $result = $default; + + // error if undefined + if ($result === NULL) { + CRIMSON_WARNING('Undefined lang string: ' . $key); + return $key; + } + + // make substitutions + if ($sub) { + if (!is_array($sub)) + $sub = [$sub]; + $result = sprintf($result, ...$sub); + } + + return $result; +} + +/** + * Returns a html element (button, a, div, ...) containing content from + * the lang string, icon, aria tags, and/or tooltips. Text content and icon + * are contained in a seconed inner html element (span, h1, ...). + * + * ilang has up to four parts: text, tooltip, icon class, and icon content. + * Each part is loaded from a different lang string using $key as the prefix. + * + * == LANG_KEYS == + * + * NAME | LANG KEY | REQUIRED | DESCRIPTION + * -------------------------------------------------------------------------- + * text | $key_text | yes | The text content of the element. Text + * | | | content will always uppercase the first + * | | | letter. + * tip | $key_tip | no | The tool tip of the element. + * icon | $key_icon | no | Adds a <i> element with the class + * | | | <lang string>. + * content | $key_content | no | If icon, adds <lang string> as the + * | | | inner html of the icon. + * + * == ARGUMENTS == + * + * NAME | REQUIRED | DEFAULT | DESCRIPTION + * --------------------------------------------------------------------------- + * $key | yes | | The key of the interface lang string. + * $class | no | | The class of the html element. + * $id | no | | The id of the html element. + * $sub | no | [] | Substitution arguments passed into lang() + * | | | in both $key_text and $key_tip. + * $type | no | 'a' | Sets the type of the html element. + * $subtype | no | 'span' | Sets the type of the inner html element. + * $attrs | no | array() | Sets html attributes using the key/value + * | | | pairs from $attrs. $class, $id, $href, and + * | | | and $onclick are all short hand for + * | | | $attrs['<name>']; Named attr arguments take + * | | | priority over any defined in $attrs. + * $style | no | | $attrs['style'] = $style. + * $href | no | | $attrs['href'] = $href. $type = 'a'; + * $onclick | no | | $attrs['onclick'] = $onclick. $type = 'button'. + * + * NOTE: For any non required argument that is falsy, it is converted back to + * its default value. + * + * NOTE: For any non required argument that does not have a default value + * listed, falsy values turn off that attribute or functionality. + * + * WARNING: $href and $onclick also modify the default $type. If $type is + * passed to ilang, that type will be used instead. + * + * WARNING: Lang strings WILL be html escaped along with each atribute value. + * Everything else will not be sanitized by this function. + */ +function ilang( + string $key, + ?string $class = NULL, + ?string $id = NULL, + array $sub = [], + ?string $type = NULL, + string $subtype = 'span', + array $attrs = array(), + ?string $style = NULL, + ?string $href = NULL, + ?string $onclick = NULL, +): string { + // read lang keys + $text = lang("{$key}_text", sub: $sub); + $tip = lang("{$key}_tip", '', sub: $sub); + $icon = lang("{$key}_icon", ''); + $content = lang("{$key}_content", ''); + + // uppercase + $text = ucfirst($text); + + // set $type if falsy + if (!$type) { + if ($href) + $type = 'a'; + else if ($onclick) + $type = 'button'; + else + $type = 'a'; + } + + // populate $attrs with named arguments + if ($tip) { + $attrs['title'] = $tip; + $attrs['aria-label'] = $tip; + } + if ($class) + $attrs['class'] = "{$class} ilang"; + else + $attrs['class'] = "ilang"; + if ($id) + $attrs['id'] = $id; + if ($style) + $attrs['style'] = $style; + if ($href) + $attrs['href'] = $href; + if ($onclick) + $attrs['onclick'] = $onclick; + + $html = ""; + // open tag + $html .= "<{$type}"; + foreach ($attrs as $key => $value) { + $value = esc($value, TRUE); // html tag & string escape + $html .= " {$key}=\"{$value}\""; + } + $html .= ">"; + // icon + if ($icon) { + $icon = esc($icon, TRUE); // html tag & string escape + $html .= "<i class=\"{$icon}\">"; + if ($content) { + $content = esc($content); // html tag escape + $html .= "{$content}"; + } + $html .= "</i>"; + } + // content + $text = esc($text); // html tag escape + $html .= "<{$subtype}>{$text}</{$subtype}>"; + // close tag + $html .= "</{$type}>"; + + return $html; +} diff --git a/src/lib/meta.php b/src/lib/meta.php new file mode 100644 index 0000000..471d3e3 --- /dev/null +++ b/src/lib/meta.php @@ -0,0 +1,28 @@ +<?php +/// CRIMSON --- A simple PHP framework. +/// Copyright © 2024 Freya Murphy <contact@freyacat.org> +/// +/// This file is part of CRIMSON. +/// +/// CRIMSON is free software; you can redistribute it and/or modify it +/// under the terms of the GNU General Public License as published by +/// the Free Software Foundation; either version 3 of the License, or (at +/// your option) any later version. +/// +/// CRIMSON is distributed in the hope that it will be useful, but +/// WITHOUT ANY WARRANTY; without even the implied warranty of +/// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +/// GNU General Public License for more details. +/// +/// You should have received a copy of the GNU General Public License +/// along with CRIMSON. If not, see <http://www.gnu.org/licenses/>. + +function CRIMSON_META(Controller $self): string { + $data = $self->CRIMSON_data; + $html = ''; + foreach ($data['css'] as $css) + $html .= $self->link_css($css); + foreach ($data['js'] as $js) + $html .= $self->link_js($js); + return $html; +} |