2) { for ($i = 2; $i < count($route); $i++) { $var = $route[$i]; list($k, $v) = explode('=', $var); $RouterInput[trim($k)] = trim($v); } } /** @var request_handler $handler */ $handler = new $handler_class(); $handler->call_act($_SERVER['REQUEST_METHOD'], $action); } function request_path(): string { $uri = $_SERVER['REQUEST_URI']; if (($pos = strpos($uri, '?')) !== false) $uri = substr($uri, 0, $pos); return $uri; } enum HTTPCode: int { case MovedPermanently = 301; case Found = 302; case Unauthorized = 401; case NotFound = 404; case Forbidden = 403; case InternalServerError = 500; case NotImplemented = 501; } function http_error(HTTPCode $http_code, string $message = ''): void { $ctx = new SkinContext('\\skin\\error'); $http_message = preg_replace('/(?name); $html = $ctx->http_error($http_code->value, $http_message, $message); http_response_code($http_code->value); echo $html; exit; } function redirect(string $url, HTTPCode $code = HTTPCode::MovedPermanently): void { if (!in_array($code, [HTTPCode::MovedPermanently, HTTPCode::Found])) internal_server_error('invalid http code'); http_response_code($code->value); header('Location: '.$url); exit; } function internal_server_error(string $message = '') { http_error(HTTPCode::InternalServerError, $message); } function not_found(string $message = '') { http_error(HTTPCode::NotFound, $message); } function forbidden(string $message = '') { http_error(HTTPCode::Forbidden, $message); } function is_xhr_request(): bool { return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest'; } function ajax_ok(mixed $data): void { ajax_response(['response' => $data]); } function ajax_error(mixed $error, int $code = 200): void { ajax_response(['error' => $error], $code); } function ajax_response(mixed $data, int $code = 200): void { header('Cache-Control: no-cache, must-revalidate'); header('Pragma: no-cache'); header('Content-Type: application/json; charset=utf-8'); http_response_code($code); echo jsonEncode($data); exit; } abstract class request_handler { function __construct() { add_static( 'css/common.css', 'js/common.js' ); add_skin_strings_re('/^theme_/'); } function before_dispatch(string $http_method, string $action) {} function call_act(string $http_method, string $action, array $input = []) { global $RouterInput; $handler_method = $_SERVER['REQUEST_METHOD'].'_'.$action; if (!method_exists($this, $handler_method)) not_found(static::class.'::'.$handler_method.' is not defined'); if (!((new ReflectionMethod($this, $handler_method))->isPublic())) not_found(static::class.'::'.$handler_method.' is not public'); if (!empty($input)) { foreach ($input as $k => $v) $RouterInput[$k] = $v; } $args = $this->before_dispatch($http_method, $action); return call_user_func_array([$this, $handler_method], is_array($args) ? [$args] : []); } } enum InputVarType: string { case INTEGER = 'i'; case FLOAT = 'f'; case BOOLEAN = 'b'; case STRING = 's'; case ENUM = 'e'; } function input(string $input): array { global $RouterInput; $input = preg_split('/,\s+?/', $input, -1, PREG_SPLIT_NO_EMPTY); $ret = []; foreach ($input as $var) { $enum_values = null; $enum_default = null; $pos = strpos($var, ':'); if ($pos !== false) { $type = substr($var, 0, $pos); $rest = substr($var, $pos + 1); $vartype = InputVarType::tryFrom($type); if (is_null($vartype)) internal_server_error('invalid input type '.$type); if ($vartype == InputVarType::ENUM) { $br_from = strpos($rest, '('); $br_to = strpos($rest, ')'); if ($br_from === false || $br_to === false) internal_server_error('failed to parse enum values: '.$rest); $enum_values = array_map('trim', explode('|', trim(substr($rest, $br_from + 1, $br_to - $br_from - 1)))); $name = trim(substr($rest, 0, $br_from)); if (!empty($enum_values)) { foreach ($enum_values as $key => $val) { if (str_starts_with($val, '=')) { $enum_values[$key] = substr($val, 1); $enum_default = $enum_values[$key]; } } } } else { $name = trim($rest); } } else { $vartype = InputVarType::STRING; $name = trim($var); } $val = null; if (isset($RouterInput[$name])) { $val = $RouterInput[$name]; } else if (isset($_POST[$name])) { $val = $_POST[$name]; } else if (isset($_GET[$name])) { $val = $_GET[$name]; } if (is_array($val)) $val = implode($val); $ret[] = match($vartype) { InputVarType::INTEGER => (int)$val, InputVarType::FLOAT => (float)$val, InputVarType::BOOLEAN => (bool)$val, InputVarType::ENUM => !in_array($val, $enum_values) ? $enum_default ?? '' : (string)$val, default => (string)$val }; } return $ret; } function csrf_get(string $key): string { return _csrf_get_token($_SERVER['REMOTE_ADDR'], $key); } function csrf_check(string $key) { if (csrf_get($key) != ($_REQUEST['token'] ?? '')) { forbidden('invalid token'); } } function _csrf_get_token(string $user_token, string $key): string { global $config; return substr(sha1($config['csrf_token'].$user_token.$key), 0, 20); }