admin: log actions

This commit is contained in:
E. S. 2024-03-09 16:20:00 +00:00
parent 68a04ab18d
commit 4a96cc6b5a
16 changed files with 726 additions and 171 deletions

View File

@ -100,6 +100,11 @@ function ensure_admin() {
forbidden(); forbidden();
} }
function ensure_xhr() {
if (!is_xhr_request())
invalid_request();
}
abstract class request_handler { abstract class request_handler {
function __construct() { function __construct() {
add_static( add_static(

View File

@ -86,13 +86,16 @@ class AdminHandler extends request_handler {
if (!uploads::isExtensionAllowed($ext)) if (!uploads::isExtensionAllowed($ext))
redirect('/admin/uploads/?error='.urlencode('extension not allowed')); redirect('/admin/uploads/?error='.urlencode('extension not allowed'));
$name = $custom_name ?: $f['name'];
$upload_id = uploads::add( $upload_id = uploads::add(
$f['tmp_name'], $f['tmp_name'],
$custom_name ?: $f['name'], $name,
$note); $note);
if (!$upload_id) if (!$upload_id)
redirect('/admin/uploads/?error='.urlencode('failed to create upload')); redirect('/admin/uploads/?error='.urlencode('failed to create upload'));
admin_log(new \AdminActions\UploadsAdd($upload_id, $name, $note));
} }
redirect('/admin/uploads/'); redirect('/admin/uploads/');
@ -105,6 +108,7 @@ class AdminHandler extends request_handler {
redirect('/admin/uploads/?error='.urlencode('upload not found')); redirect('/admin/uploads/?error='.urlencode('upload not found'));
csrf_check('delupl'.$id); csrf_check('delupl'.$id);
uploads::delete($id); uploads::delete($id);
admin_log(new \AdminActions\UploadsDelete($id));
redirect('/admin/uploads/'); redirect('/admin/uploads/');
} }
@ -118,12 +122,13 @@ class AdminHandler extends request_handler {
csrf_check('editupl'.$id); csrf_check('editupl'.$id);
$upload->setNote($note); $upload->setNote($note);
admin_log(new \AdminActions\UploadsEditNote($id, $note));
redirect('/admin/uploads/'); redirect('/admin/uploads/');
} }
function POST_ajax_md_preview() { function POST_ajax_md_preview() {
if (!is_xhr_request()) ensure_xhr();
forbidden();
list($md, $title, $use_image_previews) = input('md, title, b:use_image_previews'); list($md, $title, $use_image_previews) = input('md, title, b:use_image_previews');
$html = markup::markdownToHtml($md, $use_image_previews); $html = markup::markdownToHtml($md, $use_image_previews);
$ctx = new SkinContext('\\skin\\admin'); $ctx = new SkinContext('\\skin\\admin');
@ -177,10 +182,95 @@ class AdminHandler extends request_handler {
ajax_error(['code' => 'db_err']); ajax_error(['code' => 'db_err']);
} }
admin_log(new \AdminActions\PageCreate($name));
$page = pages::getByName($name); $page = pages::getByName($name);
ajax_ok(['url' => $page->getUrl()]); 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() { function GET_post_add() {
add_skin_strings_re('/^(err_)?blog_/'); add_skin_strings_re('/^(err_)?blog_/');
set_title('$blog_write'); set_title('$blog_write');
@ -205,9 +295,7 @@ class AdminHandler extends request_handler {
} }
function POST_post_add() { function POST_post_add() {
if (!is_xhr_request()) ensure_xhr();
invalid_request();
csrf_check('post_add'); csrf_check('post_add');
list($visibility_enabled, $short_name, $langs, $date) 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']); ajax_error(['code' => 'db_err', 'message' => 'failed to add post']);
// add texts // add texts
$added_texts = []; // for admin actions logging, at the end
foreach ($lang_data as $lang => $data) { foreach ($lang_data as $lang => $data) {
list($title, $text, $toc_enabled) = $data; list($title, $text, $toc_enabled) = $data;
if (!$post->addText( if (!($new_post_text = $post->addText(
lang: PostLanguage::from($lang), lang: PostLanguage::from($lang),
title: $title, title: $title,
md: $text, md: $text,
toc: $toc_enabled) toc: $toc_enabled))
) { ) {
posts::delete($post); posts::delete($post);
ajax_error(['code' => 'db_err', 'message' => 'failed to add text language '.$lang]); 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 // done
ajax_ok(['url' => $post->getUrl()]); 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() { function GET_post_delete() {
list($name) = input('short_name'); list($name) = input('short_name');
@ -289,8 +365,10 @@ class AdminHandler extends request_handler {
if (!$post) if (!$post)
not_found(); not_found();
csrf_check('delpost'.$post->id); $id = $post->id;
csrf_check('delpost'.$id);
posts::delete($post); posts::delete($post);
admin_log(new \AdminActions\PostDelete($id));
redirect('/articles/', code: HTTPCode::Found); redirect('/articles/', code: HTTPCode::Found);
} }
@ -299,7 +377,9 @@ class AdminHandler extends request_handler {
$lang = PostLanguage::from($lang); $lang = PostLanguage::from($lang);
$post = posts::getByName($short_name); $post = posts::getByName($short_name);
if ($post) { if (!$post)
not_found();
$texts = $post->getTexts(); $texts = $post->getTexts();
if (!isset($texts[$lang->value])) if (!isset($texts[$lang->value]))
not_found(); not_found();
@ -345,12 +425,8 @@ class AdminHandler extends request_handler {
); );
} }
not_found();
}
function POST_post_edit() { function POST_post_edit() {
if (!is_xhr_request()) ensure_xhr();
invalid_request();
list($old_short_name, $short_name, $langs, $date) = input('short_name, new_short_name, langs, date'); 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_data['short_name'] = $short_name;
$post->edit($post_data); $post->edit($post_data);
admin_log(new \AdminActions\PostEdit($post->id));
ajax_ok(['url' => $post->getUrl().'edit/?saved=1&lang='.$lang->value]); ajax_ok(['url' => $post->getUrl().'edit/?saved=1&lang='.$lang->value]);
} }
function GET_page_edit() { protected static function _postEditValidateCommonData($date) {
list($short_name, $saved) = input('short_name, b:saved'); $dt = DateTime::createFromFormat("Y-m-d", $date);
$date_is_valid = $dt && $dt->format("Y-m-d") === $date;
$page = pages::getByName($short_name); if (!$date_is_valid)
if (!$page) ajax_error(['code' => 'no_date']);
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 make_wide() { protected static function make_wide() {

View File

@ -22,10 +22,13 @@ spl_autoload_register(function($class) {
'engine/skin' => ['SkinContext'], 'engine/skin' => ['SkinContext'],
]; ];
if (str_contains($class, '\\'))
$class = str_replace('\\', '/', $class);
$path = null; $path = null;
foreach (['Handler', 'Helper'] as $sfx) { foreach (['Handler', 'Helper'] as $sfx) {
if (str_ends_with($class, $sfx)) { if (str_ends_with($class, $sfx)) {
$path = APP_ROOT.'/'.strtolower($sfx).'/'.str_replace('\\', '/', $class).'.php'; $path = APP_ROOT.'/'.strtolower($sfx).'/'.$class.'.php';
break; break;
} }
} }

View File

@ -0,0 +1,91 @@
<?php
namespace AdminActions;
abstract class BaseAction {
protected int $recordId;
protected ?int $adminId;
protected int $timeStamp;
protected int $ip;
protected bool $isCli;
protected static array $hashActionIds = [];
public static function getActionId(): int {
if (!array_key_exists(static::class, self::$hashActionIds)) {
$class = static::class;
if (($pos = strrpos($class, '\\')) !== false)
$class = substr($class, $pos+1);
$hash = hash('adler32', $class, true);
self::$hashActionIds[static::class] = unpack('N', $hash)[1];
}
return self::$hashActionIds[static::class];
}
public function getActionName(): string {
$cl = get_class($this);
if (($pos = strrpos($cl, '\\')) !== false)
$cl = substr($cl, $pos+1);
return $cl;
}
public function setMetaInformation(int $record_id,
?int $admin_id,
int $timestamp,
int $ip,
bool $is_cli) {
$this->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('<br>', $lines);
}*/
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class PageCreate extends BaseAction {
public function __construct(
public string $name
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class PageDelete extends BaseAction {
public function __construct(
public string $name
) {}
}

View File

@ -0,0 +1,12 @@
<?php
namespace AdminActions;
class PageEdit extends BaseAction {
public function __construct(
public string $oldName,
public ?string $newName
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class PostCreate extends BaseAction {
public function __construct(
public int $id
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class PostDelete extends BaseAction {
public function __construct(
public int $id
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class PostEdit extends BaseAction {
public function __construct(
public int $id
) {}
}

View File

@ -0,0 +1,13 @@
<?php
namespace AdminActions;
class PostTextCreate extends BaseAction {
public function __construct(
public int $id,
public int $postId,
public string $lang
) {}
}

View File

@ -0,0 +1,13 @@
<?php
namespace AdminActions;
class UploadsAdd extends BaseAction {
public function __construct(
public int $uploadId,
public string $name,
public string $note
) {}
}

View File

@ -0,0 +1,11 @@
<?php
namespace AdminActions;
class UploadsDelete extends BaseAction {
public function __construct(
public int $uploadId,
) {}
}

View File

@ -0,0 +1,12 @@
<?php
namespace AdminActions;
class UploadsEditNote extends BaseAction {
public function __construct(
public int $uploadId,
public string $note
) {}
}

View File

@ -0,0 +1,318 @@
<?php
namespace AdminActions\util;
use AdminActions\BaseAction;
use ReflectionClass;
use ReflectionProperty;
class Logger {
const TABLE = 'admin_actions';
const INTS_COUNT = 6;
const INTS_PREFIX = 'i';
const VARCHARS_COUNT = 2;
const VARCHARS_PREFIX = 'c';
const SERIALIZED_COUNT = 1;
const SERIALIZED_PREFIX = 's';
protected static ?array $classes = null;
public static function record(BaseAction $action): int {
$packed = self::pack($action);
$data = [
'action' => $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;
}
}

View File

@ -3,6 +3,7 @@
const ADMIN_SESSION_TIMEOUT = 86400 * 14; const ADMIN_SESSION_TIMEOUT = 86400 * 14;
const ADMIN_COOKIE_NAME = 'admin_key'; const ADMIN_COOKIE_NAME = 'admin_key';
const ADMIN_LOGIN_MAX_LENGTH = 32; const ADMIN_LOGIN_MAX_LENGTH = 32;
$AdminSession = [ $AdminSession = [
'id' => null, 'id' => null,
'auth_id' => 0, '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 { function admin_exists(string $login): bool {
$db = DB(); $db = DB();
return (int)$db->result($db->query("SELECT COUNT(*) FROM admins WHERE login=? LIMIT 1", $login)) > 0; 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; return $db->numRows($q) > 0 ? (int)$db->result($q) : null;
} }
function admin_set_password(string $login, string $password): bool { function admin_set_password(string $login, string $password): bool {
$db = DB(); $db = DB();
$db->query("UPDATE admins SET password=? WHERE login=?", salt_password($password), $login); $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, 'login' => $login,
]; ];
admin_set_cookie($token); _admin_set_cookie($token);
return true; return true;
} }
@ -135,15 +109,45 @@ function admin_logout() {
$AdminSession['login'] = null; $AdminSession['login'] = null;
$AdminSession['auth_id'] = 0; $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; global $config;
setcookie(ADMIN_COOKIE_NAME, $token, time() + ADMIN_SESSION_TIMEOUT, '/', $config['cookie_host']); setcookie(ADMIN_COOKIE_NAME, $token, time() + ADMIN_SESSION_TIMEOUT, '/', $config['cookie_host']);
} }
function admin_unset_cookie(): void { function _admin_unset_cookie(): void {
global $config; global $config;
setcookie(ADMIN_COOKIE_NAME, '', 1, '/', $config['cookie_host']); setcookie(ADMIN_COOKIE_NAME, '', 1, '/', $config['cookie_host']);
} }