diff --git a/engine/request.php b/engine/request.php index 2992fbf..1af2f0c 100644 --- a/engine/request.php +++ b/engine/request.php @@ -100,6 +100,11 @@ function ensure_admin() { forbidden(); } +function ensure_xhr() { + if (!is_xhr_request()) + invalid_request(); +} + abstract class request_handler { function __construct() { add_static( diff --git a/handler/AdminHandler.php b/handler/AdminHandler.php index bf44bfd..e5b9aab 100644 --- a/handler/AdminHandler.php +++ b/handler/AdminHandler.php @@ -86,13 +86,16 @@ class AdminHandler extends request_handler { if (!uploads::isExtensionAllowed($ext)) redirect('/admin/uploads/?error='.urlencode('extension not allowed')); + $name = $custom_name ?: $f['name']; $upload_id = uploads::add( $f['tmp_name'], - $custom_name ?: $f['name'], + $name, $note); if (!$upload_id) redirect('/admin/uploads/?error='.urlencode('failed to create upload')); + + admin_log(new \AdminActions\UploadsAdd($upload_id, $name, $note)); } redirect('/admin/uploads/'); @@ -105,6 +108,7 @@ class AdminHandler extends request_handler { redirect('/admin/uploads/?error='.urlencode('upload not found')); csrf_check('delupl'.$id); uploads::delete($id); + admin_log(new \AdminActions\UploadsDelete($id)); redirect('/admin/uploads/'); } @@ -118,12 +122,13 @@ class AdminHandler extends request_handler { csrf_check('editupl'.$id); $upload->setNote($note); + + admin_log(new \AdminActions\UploadsEditNote($id, $note)); redirect('/admin/uploads/'); } function POST_ajax_md_preview() { - if (!is_xhr_request()) - forbidden(); + ensure_xhr(); list($md, $title, $use_image_previews) = input('md, title, b:use_image_previews'); $html = markup::markdownToHtml($md, $use_image_previews); $ctx = new SkinContext('\\skin\\admin'); @@ -177,10 +182,95 @@ class AdminHandler extends request_handler { ajax_error(['code' => 'db_err']); } + admin_log(new \AdminActions\PageCreate($name)); + $page = pages::getByName($name); ajax_ok(['url' => $page->getUrl()]); } + function GET_page_delete() { + list($name) = input('short_name'); + + $page = pages::getByName($name); + if (!$page) + not_found(); + + $url = $page->getUrl(); + + csrf_check('delpage'.$page->shortName); + pages::delete($page); + admin_log(new \AdminActions\PageDelete($name)); + redirect($url, code: HTTPCode::Found); + } + + function GET_page_edit() { + list($short_name, $saved) = input('short_name, b:saved'); + + $page = pages::getByName($short_name); + if (!$page) + not_found(); + + add_skin_strings_re('/^(err_)?pages_/'); + add_skin_strings_re('/^(err_)?blog_/'); + set_title(lang('pages_page_edit_title', $page->shortName.'.html')); + static::make_wide(); + $js_text = [ + 'text' => $page->md, + 'title' => $page->title, + ]; + render('admin/pageForm', + is_edit: true, + short_name: $page->shortName, + title: $page->title, + text: $page->md, + visible: $page->visible, + saved: $saved, + langs: PostLanguage::cases(), + js_text: $js_text); + } + + function POST_page_edit() { + ensure_xhr(); + + list($short_name) = input('short_name'); + + $page = pages::getByName($short_name); + if (!$page) + not_found(); + + csrf_check('editpage'.$page->shortName); + + list($text, $title, $visible, $short_name) + = input('text, title, b:visible, new_short_name'); + + $text = trim($text); + $title = trim($title); + $error_code = null; + + if (!$title) { + $error_code = 'no_title'; + } else if (!$text) { + $error_code = 'no_text'; + } else if (!$short_name) { + $error_code = 'no_short_name'; + } + + if ($error_code) + ajax_error(['code' => $error_code]); + + $new_short_name = $page->shortName != $short_name ? $short_name : null; + + $page->edit([ + 'title' => $title, + 'md' => $text, + 'visible' => (int)$visible, + 'short_name' => $short_name, + ]); + + admin_log(new \AdminActions\PageEdit($short_name, $new_short_name)); + ajax_ok(['url' => $page->getUrl().'edit/?saved=1']); + } + function GET_post_add() { add_skin_strings_re('/^(err_)?blog_/'); set_title('$blog_write'); @@ -205,9 +295,7 @@ class AdminHandler extends request_handler { } function POST_post_add() { - if (!is_xhr_request()) - invalid_request(); - + ensure_xhr(); csrf_check('post_add'); list($visibility_enabled, $short_name, $langs, $date) @@ -244,44 +332,32 @@ class AdminHandler extends request_handler { ajax_error(['code' => 'db_err', 'message' => 'failed to add post']); // add texts + $added_texts = []; // for admin actions logging, at the end foreach ($lang_data as $lang => $data) { list($title, $text, $toc_enabled) = $data; - if (!$post->addText( + if (!($new_post_text = $post->addText( lang: PostLanguage::from($lang), title: $title, md: $text, - toc: $toc_enabled) + toc: $toc_enabled)) ) { posts::delete($post); ajax_error(['code' => 'db_err', 'message' => 'failed to add text language '.$lang]); + } else { + $added_texts[] = [$new_post_text->id, $lang]; } } + admin_log(new \AdminActions\PostCreate($post->id)); + foreach ($added_texts as $added_text) { + list($id, $lang) = $added_text; + admin_log(new \AdminActions\PostTextCreate($id, $post->id, $lang)); + } + // done ajax_ok(['url' => $post->getUrl()]); } - protected static function _postEditValidateCommonData($date) { - $dt = DateTime::createFromFormat("Y-m-d", $date); - $date_is_valid = $dt && $dt->format("Y-m-d") === $date; - if (!$date_is_valid) - ajax_error(['code' => 'no_date']); - } - - function GET_page_delete() { - list($name) = input('short_name'); - - $page = pages::getByName($name); - if (!$page) - not_found(); - - $url = $page->getUrl(); - - csrf_check('delpage'.$page->shortName); - pages::delete($page); - redirect($url, code: HTTPCode::Found); - } - function GET_post_delete() { list($name) = input('short_name'); @@ -289,8 +365,10 @@ class AdminHandler extends request_handler { if (!$post) not_found(); - csrf_check('delpost'.$post->id); + $id = $post->id; + csrf_check('delpost'.$id); posts::delete($post); + admin_log(new \AdminActions\PostDelete($id)); redirect('/articles/', code: HTTPCode::Found); } @@ -299,58 +377,56 @@ class AdminHandler extends request_handler { $lang = PostLanguage::from($lang); $post = posts::getByName($short_name); - if ($post) { - $texts = $post->getTexts(); - if (!isset($texts[$lang->value])) - not_found(); + if (!$post) + not_found(); - $js_texts = []; - foreach (PostLanguage::cases() as $pl) { - if (isset($texts[$pl->value])) { - $text = $texts[$pl->value]; - $js_texts[$pl->value] = [ - 'title' => $text->title, - 'md' => $text->md, - 'toc' => $text->toc, - ]; - } else { - $js_texts[$pl->value] = [ - 'title' => '', - 'md' => '', - 'toc' => false, - ]; - } + $texts = $post->getTexts(); + if (!isset($texts[$lang->value])) + not_found(); + + $js_texts = []; + foreach (PostLanguage::cases() as $pl) { + if (isset($texts[$pl->value])) { + $text = $texts[$pl->value]; + $js_texts[$pl->value] = [ + 'title' => $text->title, + 'md' => $text->md, + 'toc' => $text->toc, + ]; + } else { + $js_texts[$pl->value] = [ + 'title' => '', + 'md' => '', + 'toc' => false, + ]; } - - $text = $texts[$lang->value]; - - add_skin_strings_re('/^(err_)?blog_/'); - add_skin_strings(['blog_post_edit_title']); - set_title(lang('blog_post_edit_title', $text->title)); - static::make_wide(); - render('admin/postForm', - is_edit: true, - post_id: $post->id, - post_url: $post->getUrl(), - title: $text->title, - text: $text->md, - date: $post->getDateForInputField(), - visible: $post->visible, - toc: $text->toc, - saved: $saved, - short_name: $short_name, - langs: PostLanguage::cases(), - lang: $text->lang->value, - js_texts: $js_texts - ); } - not_found(); + $text = $texts[$lang->value]; + + add_skin_strings_re('/^(err_)?blog_/'); + add_skin_strings(['blog_post_edit_title']); + set_title(lang('blog_post_edit_title', $text->title)); + static::make_wide(); + render('admin/postForm', + is_edit: true, + post_id: $post->id, + post_url: $post->getUrl(), + title: $text->title, + text: $text->md, + date: $post->getDateForInputField(), + visible: $post->visible, + toc: $text->toc, + saved: $saved, + short_name: $short_name, + langs: PostLanguage::cases(), + lang: $text->lang->value, + js_texts: $js_texts + ); } function POST_post_edit() { - if (!is_xhr_request()) - invalid_request(); + ensure_xhr(); list($old_short_name, $short_name, $langs, $date) = input('short_name, new_short_name, langs, date'); @@ -401,74 +477,16 @@ class AdminHandler extends request_handler { $post_data['short_name'] = $short_name; $post->edit($post_data); + admin_log(new \AdminActions\PostEdit($post->id)); ajax_ok(['url' => $post->getUrl().'edit/?saved=1&lang='.$lang->value]); } - function GET_page_edit() { - list($short_name, $saved) = input('short_name, b:saved'); - - $page = pages::getByName($short_name); - if (!$page) - not_found(); - - add_skin_strings_re('/^(err_)?pages_/'); - add_skin_strings_re('/^(err_)?blog_/'); - set_title(lang('pages_page_edit_title', $page->shortName.'.html')); - static::make_wide(); - $js_text = [ - 'text' => $page->md, - 'title' => $page->title, - ]; - render('admin/pageForm', - is_edit: true, - short_name: $page->shortName, - title: $page->title, - text: $page->md, - visible: $page->visible, - saved: $saved, - langs: PostLanguage::cases(), - js_text: $js_text); - } - - function POST_page_edit() { - if (!is_xhr_request()) - forbidden(); - - list($short_name) = input('short_name'); - - $page = pages::getByName($short_name); - if (!$page) - not_found(); - - csrf_check('editpage'.$page->shortName); - - list($text, $title, $visible, $short_name) - = input('text, title, b:visible, new_short_name'); - - $text = trim($text); - $title = trim($title); - $error_code = null; - - if (!$title) { - $error_code = 'no_title'; - } else if (!$text) { - $error_code = 'no_text'; - } else if (!$short_name) { - $error_code = 'no_short_name'; - } - - if ($error_code) - ajax_error(['code' => $error_code]); - - $page->edit([ - 'title' => $title, - 'md' => $text, - 'visible' => (int)$visible, - 'short_name' => $short_name, - ]); - - ajax_ok(['url' => $page->getUrl().'edit/?saved=1']); + protected static function _postEditValidateCommonData($date) { + $dt = DateTime::createFromFormat("Y-m-d", $date); + $date_is_valid = $dt && $dt->format("Y-m-d") === $date; + if (!$date_is_valid) + ajax_error(['code' => 'no_date']); } protected static function make_wide() { diff --git a/init.php b/init.php index a3b58cd..bb7f053 100644 --- a/init.php +++ b/init.php @@ -22,10 +22,13 @@ spl_autoload_register(function($class) { 'engine/skin' => ['SkinContext'], ]; + if (str_contains($class, '\\')) + $class = str_replace('\\', '/', $class); + $path = null; foreach (['Handler', 'Helper'] as $sfx) { if (str_ends_with($class, $sfx)) { - $path = APP_ROOT.'/'.strtolower($sfx).'/'.str_replace('\\', '/', $class).'.php'; + $path = APP_ROOT.'/'.strtolower($sfx).'/'.$class.'.php'; break; } } diff --git a/lib/AdminActions/BaseAction.php b/lib/AdminActions/BaseAction.php new file mode 100644 index 0000000..99adbf0 --- /dev/null +++ b/lib/AdminActions/BaseAction.php @@ -0,0 +1,91 @@ +recordId = $record_id; + $this->adminId = $admin_id; + $this->timeStamp = $timestamp; + $this->ip = $ip; + $this->isCli = $is_cli; + } + + public function getActorInfo(): array { + return [$this->adminId, $this->isCli]; + } + + public function getAdminId(): ?int { + return $this->adminId; + } + + public function isCommandLineAction(): bool { + return $this->isCli; + } + + //public function getDate(): string { + // return formatTime($this->timeStamp, ['short_months' => true]); + //} + + public function getTimeStamp(): int { + return $this->timeStamp; + } + + public function getIPv4(): string { + return ulong2ip($this->ip); + } + + public function getRecordId(): int { + return $this->recordId; + } + + /*function renderHtml(): string { + $rc = new \ReflectionClass($this); + $lines = []; + $fields = $rc->getProperties(\ReflectionProperty::IS_PUBLIC); + foreach ($fields as $field) { + $name = $field->getName(); + $rfield = new \ReflectionProperty(get_class($this), $name); + if ($rfield->getType()->allowsNull() && is_null($this->{$name})) + continue; + if (is_array($this->{$name})) { + $val = htmlescape(jsonEncode($this->{$name})); + } else { + $val = htmlescape($this->{$name}); + } + $lines[] = $name.'='.$val; + } + return implode('
', $lines); + }*/ +} \ No newline at end of file diff --git a/lib/AdminActions/PageCreate.php b/lib/AdminActions/PageCreate.php new file mode 100644 index 0000000..31c02f6 --- /dev/null +++ b/lib/AdminActions/PageCreate.php @@ -0,0 +1,11 @@ + $action::getActionId(), + 'ts' => time(), + ]; + + if (is_cli()) { + $data += [ + 'cli' => 1, + ]; + } else { + $data += [ + 'admin_id' => admin_current_info()['id'], + 'ip' => !empty($_SERVER['REMOTE_ADDR']) ? ip2ulong($_SERVER['REMOTE_ADDR']) : 0, + ]; + } + + foreach ($packed as $prefix => $args) { + foreach ($args as $i => $arg) { + $name = $prefix.'arg'.($i+1); + $data[$name] = $arg; + } + } + + $db = DB(); + $db->insert(self::TABLE, $data); + + return $db->insertId(); + } + + public static function getRecordById(int $id): ?BaseAction { + $db = DB(); + $q = $db->query("SELECT * FROM ".self::TABLE." WHERE id=?", $id); + if (!$db->numRows($q)) + return null; + return self::unpack($db->fetch($q)); + } + + public static function getRecordsCount(?array $admin_types = null, + ?array $actions = null, + ?array $arguments = null): int { + $db = DB(); + $sql = "SELECT COUNT(*) FROM ".self::TABLE; + $where = self::getSQLSelectConditions($admin_types, $actions, $arguments); + if ($where != '') + $sql .= " WHERE ".$where; + $q = $db->query($sql); + return (int)$db->result($q); + } + + /** + * @param int $offset + * @param int $count + * @param array|null $admin_types + * @param array|null $actions + * @param array|null $arguments + * @return BaseAction[] + */ + public static function getRecords(int $offset, + int $count, + ?array $admin_types = null, + ?array $actions = null, + ?array $arguments = null): array { + $db = DB(); + $sql = "SELECT * FROM ".self::TABLE; + $where = self::getSQLSelectConditions($admin_types, $actions, $arguments); + if ($where != '') + $sql .= " WHERE ".$where; + $sql .= " ORDER BY ts DESC"; + $sql .= " LIMIT $offset, $count"; + return array_map(self::class.'::unpack', $db->fetchAll($db->query($sql))); + } + + /** + * @param int $user_id + * @param int|null $time_from + * @param int|null $time_to + * @return BaseAction[] + */ + public static function getUserRecords(int $user_id, ?int $time_from, ?int $time_to): array { + $db = DB(); + $sql = "SELECT * FROM ".self::TABLE." WHERE admin_id={$user_id}"; + if ($time_from && $time_to) + $sql .= " AND ts BETWEEN {$time_from} AND {$time_to} "; + $sql .= "ORDER BY ts"; + return array_map(self::class.'::unpack', $db->fetchAll($db->query($sql))); + } + + protected static function getSQLSelectConditions(?array $admin_types = null, + ?array $actions = null, + ?array $arguments = null): string { + $wheres = []; + $db = DB(); + + if (!empty($admin_types)) + $wheres[] = "admin_type IN ('".implode("', '", $admin_types)."')"; + + if (!empty($actions)) { + $actions = array_map( + /** @var BaseAction|int $action */ + fn($action) => is_string($action) ? $action::getActionId() : $action, $actions); + $wheres[] = "action IN (".implode(',', $actions).")"; + } + + if (!empty($arguments)) { + foreach ($arguments as $k => $v) { + if (!str_starts_with($k, 'iarg') && !str_starts_with($k, 'carg') && !str_starts_with($k, 'sarg')) { + logError(__METHOD__.': filter argument '.$k.' not supported'); + continue; + } + $wheres[] = $v === null ? $k."=NULL" : $k."='".$db->escape($v)."'"; + } + } + + return !empty($wheres) ? "(".implode(") AND (", $wheres).")" : ''; + } + + public static function pack(BaseAction $action): array { + $field_types = self::getFieldTypes(); + $packed = []; + $cl = get_class($action); + $rc = new ReflectionClass($cl); + $fields = $rc->getProperties(ReflectionProperty::IS_PUBLIC); + foreach ($fields as $field) { + $field_name = $field->getName(); + $refl_field = new ReflectionProperty($cl, $field_name); + $refl_type = $refl_field->getType(); + if (!$refl_type->isBuiltin()) { + logError(__METHOD__.': field "'.$field_name.'" is not of built-in type'); + continue; + } + + switch ($refl_type->getName()) { + case 'int': + case 'bool': + $prefix = 'i'; + break; + case 'array': + $prefix = 's'; + break; + case 'string': + $prefix = 'c'; + break; + default: + logError(__METHOD__.': unexpected field type: '.$refl_type->getName()); + break; + } + + if (!isset($packed[$prefix]) || count($packed[$prefix]) < $field_types[$prefix]['count']) { + $packed[$prefix][] = $field_types[$prefix]['packer']($action->{$field_name}); + } else { + logError(__METHOD__.': max ['.$prefix.'] count ('.$field_types[$prefix]['count'].') exceeded'); + } + } + return $packed; + } + + public static function unpack(array $data): BaseAction { + $action_id = (int)$data['action']; + $cl = self::getClassByActionId($action_id); + + $field_types = self::getFieldTypes(); + $counter = []; + foreach ($field_types as $type => $tmp) + $counter[$type] = 1; + + $rc = new ReflectionClass($cl); + $arguments = []; + $fields = $rc->getProperties(ReflectionProperty::IS_PUBLIC); + foreach ($fields as $field) { + $name = $field->getName(); + $refl_field = new ReflectionProperty($cl, $name); + $refl_type = $refl_field->getType(); + if (!$refl_type->isBuiltin()) { + logError(__METHOD__.': field "'.$name.'" is not of built-in type'); + continue; + } + + switch ($refl_type->getName()) { + case 'int': + case 'bool': + $prefix = 'i'; + break; + case 'array': + $prefix = 's'; + break; + case 'string': + $prefix = 'c'; + break; + default: + logError(__METHOD__.': unexpected field type: '.$refl_type->getName()); + break; + } + + $val = $data[$prefix.'arg'.($counter[$prefix]++)]; + if (!$refl_type->allowsNull() || !is_null($val)) + $val = $field_types[$prefix]['unpacker']($val); + + $arguments[] = $val; + } + + /** @var BaseAction $obj */ + try { + $obj = new $cl(...$arguments); + } catch (\TypeError $e) { + logDebug($arguments); + logError($e); + exit(); + } + $obj->setMetaInformation((int)$data['id'], (int)$data['admin_id'], (int)$data['ts'], (int)$data['ip'], (bool)$data['cli']); + + return $obj; + } + + public static function getActions(bool $only_names = false): array { + if (is_null(self::$classes)) { + $objects = []; + $dir = realpath(__DIR__.'/../'); + $files = scandir($dir); + foreach ($files as $f) { + // skip non-files + if ($f == '.' || $f == '..') + continue; + + $class_name = substr($f, 0, strpos($f, '.')); + $class = '\\knigavuhe\\AdminActions\\'.$class_name; + + if (interface_exists($class) || !class_exists($class)) { + // logError(__METHOD__.': class '.$class.' not found'); + continue; + } + + $parents = class_parents($class); + $found = false; + foreach ($parents as $p) { + if (str_ends_with($p, 'BaseAction')) { + $found = true; + break; + } + } + if (!$found) { + // logError(__METHOD__.': parent BaseAction not found in class '.$class); + continue; + } + + $objects[$class::getActionId()] = $class; + } + self::$classes = $objects; + } + + if (!$only_names) + return self::$classes; + + return array_map(function(string $cl): string { + if (($pos = strrpos($cl, '\\')) !== false) + $cl = substr($cl, $pos+1); + return $cl; + }, self::$classes); + } + + public static function getClassByActionId(int $action_id): string { + return self::getActions()[$action_id]; + } + + public static function getFieldTypes(): array { + static $types = []; + if (!empty($types)) + return $types; + + foreach (['INTS', 'VARCHARS', 'SERIALIZED'] as $name) { + $prefix = constant("self::{$name}_PREFIX"); + $count = constant("self::{$name}_COUNT"); + $types[$prefix] = ['count' => $count]; + switch ($prefix) { + case 'i': + $types[$prefix]['unpacker'] = fn($v) => $v === null ? null : intval($v); + $types[$prefix]['packer'] = fn($v) => strval($v); + break; + case 'c': + $types[$prefix]['unpacker'] = fn($v) => $v === null ? null : strval($v); + $types[$prefix]['packer'] = $types[$prefix]['unpacker']; + break; + case 's': + $types[$prefix]['unpacker'] = fn($v) => $v === null ? null : unserialize($v); + $types[$prefix]['packer'] = fn($v) => serialize($v); + break; + } + } + return $types; + } + +} diff --git a/lib/admin.php b/lib/admin.php index 062d16d..2f9669c 100644 --- a/lib/admin.php +++ b/lib/admin.php @@ -3,6 +3,7 @@ const ADMIN_SESSION_TIMEOUT = 86400 * 14; const ADMIN_COOKIE_NAME = 'admin_key'; const ADMIN_LOGIN_MAX_LENGTH = 32; + $AdminSession = [ 'id' => null, 'auth_id' => 0, @@ -24,32 +25,6 @@ function admin_current_info(): array { ]; } -function _admin_check(): void { - if (!isset($_COOKIE[ADMIN_COOKIE_NAME])) - return; - - $cookie = (string)$_COOKIE[ADMIN_COOKIE_NAME]; - $db = DB(); - $q = $db->query("SELECT - admin_auth.id AS auth_id, - admin_auth.admin_id AS id, - admins.login AS login - FROM admin_auth - LEFT JOIN admins ON admin_auth.admin_id=admins.id - WHERE admin_auth.token=? - LIMIT 1", $cookie); - - if (!$db->numRows($q)) - return; - - $info = $db->fetch($q); - - global $AdminSession; - $AdminSession['id'] = (int)$info['id']; - $AdminSession['login'] = $info['login']; - $AdminSession['auth_id'] = (int)$info['auth_id']; -} - function admin_exists(string $login): bool { $db = DB(); return (int)$db->result($db->query("SELECT COUNT(*) FROM admins WHERE login=? LIMIT 1", $login)) > 0; @@ -78,7 +53,6 @@ function admin_get_id_by_login(string $login): ?int { return $db->numRows($q) > 0 ? (int)$db->result($q) : null; } - function admin_set_password(string $login, string $password): bool { $db = DB(); $db->query("UPDATE admins SET password=? WHERE login=?", salt_password($password), $login); @@ -118,7 +92,7 @@ function admin_auth(string $login, string $password): bool { 'login' => $login, ]; - admin_set_cookie($token); + _admin_set_cookie($token); return true; } @@ -135,15 +109,45 @@ function admin_logout() { $AdminSession['login'] = null; $AdminSession['auth_id'] = 0; - admin_unset_cookie(); + _admin_unset_cookie(); } -function admin_set_cookie(string $token): void { +function admin_log(\AdminActions\BaseAction $action) { + \AdminActions\Util\Logger::record($action); +} + +function _admin_check(): void { + if (!isset($_COOKIE[ADMIN_COOKIE_NAME])) + return; + + $cookie = (string)$_COOKIE[ADMIN_COOKIE_NAME]; + $db = DB(); + $q = $db->query("SELECT + admin_auth.id AS auth_id, + admin_auth.admin_id AS id, + admins.login AS login + FROM admin_auth + LEFT JOIN admins ON admin_auth.admin_id=admins.id + WHERE admin_auth.token=? + LIMIT 1", $cookie); + + if (!$db->numRows($q)) + return; + + $info = $db->fetch($q); + + global $AdminSession; + $AdminSession['id'] = (int)$info['id']; + $AdminSession['login'] = $info['login']; + $AdminSession['auth_id'] = (int)$info['auth_id']; +} + +function _admin_set_cookie(string $token): void { global $config; setcookie(ADMIN_COOKIE_NAME, $token, time() + ADMIN_SESSION_TIMEOUT, '/', $config['cookie_host']); } -function admin_unset_cookie(): void { +function _admin_unset_cookie(): void { global $config; setcookie(ADMIN_COOKIE_NAME, '', 1, '/', $config['cookie_host']); }