4in1_ws_web/handler/AdminHandler.php

715 lines
21 KiB
PHP

<?php
class AdminHandler extends request_handler {
function __construct() {
parent::__construct();
add_static('css/admin.css', 'js/admin.js');
add_skin_strings(['error']);
set_skin_opts(['inside_admin_interface' => true]);
}
function before_dispatch(string $http_method, string $action) {
if ($action != 'login' && !is_admin())
forbidden();
}
function GET_index() {
global $AdminSession;
//$admin_info = admin_current_info();
set_title('$admin_title');
render('admin/index',
admin_login: $AdminSession->login);
}
function GET_login() {
if (is_admin())
redirect('/admin/');
set_title('$admin_title');
render('admin/login');
}
function POST_login() {
csrf_check('adminlogin');
list($login, $password) = input('login, password');
admin_auth($login, $password)
? redirect('/admin/')
: forbidden();
}
function GET_logout() {
csrf_check('logout');
admin_logout();
redirect('/admin/login/', HTTPCode::Found);
}
function GET_errors() {
list($ip, $query, $url_query, $file_query, $line_query, $per_page)
= input('i:ip, query, url_query, file_query, i:line_query, i:per_page');
if (!$per_page)
$per_page = 100;
$db = DB();
$query = trim($query ?? '');
$url_query = trim($url_query ?? '');
$sql_where = [];
if ($ip) {
$sql_where[] = "ip='".$db->escape($ip)."'";
}
if ($query) {
$sql_where[] = "text LIKE '%".$db->escape($query)."%'";
}
if ($url_query) {
$sql_where[] = "url LIKE '%".$db->escape($url_query)."%'";
}
if ($file_query) {
$sql_where[] = "file LIKE '%".$db->escape($file_query)."%'";
}
if ($line_query) {
$sql_where[] = "line='".$db->escape($line_query)."'";
}
if (!empty($sql_where)) {
$sql_where = " WHERE ".implode(' AND ', $sql_where)." ";
} else {
$sql_where = '';
}
$count = (int)$db->result($db->query("SELECT COUNT(*) FROM backend_errors".$sql_where));
list($page, $pages, $offset) = get_page($per_page, $count);
$q = $db->query("SELECT *, INET_NTOA(ip) ip_s FROM backend_errors $sql_where ORDER BY id DESC LIMIT $offset, $per_page");
$list = [];
while ($row = $db->fetch($q)) {
$row['date'] = format_time($row['ts'], [
'seconds' => true,
'short_months' => true,
]);
$row['full_url'] = !str_starts_with($row['url'], 'https://') ? 'https://'.$row['url'] : $row['url'];
$error_name = getPHPErrorName((int)$row['errno']);
if (!is_null($error_name))
$row['errtype'] = $error_name;
$list[] = $row;
}
// url for pageNav
$url = '/admin/errors/';
$url_params = [];
if ($ip) {
$url_params['ip'] = $ip;
}
if ($query) {
$url_params['query'] = $query;
}
if ($url_query) {
$url_params['url_query'] = $url_query;
}
if ($file_query) {
$url_params['file_query'] = $file_query;
}
if ($line_query) {
$url_params['line_query'] = $line_query;
}
if (!empty($url_params)) {
$url .= '?'.http_build_query($url_params).'&';
} else {
$url .= '?';
}
$vars = [
'list' => $list,
'count' => $count,
'pn_page' => $page,
'pn_pages' => $pages,
'url' => $url,
];
if ($ip) {
$vars += [
'ip_filter' => ulong2ip($ip),
'ip' => $ip
];
}
$query_var_names = ['query', 'url_query', 'file_query', 'line_query'];
foreach ($query_var_names as $query_var_name) {
if ($$query_var_name) {
$vars += [$query_var_name => $$query_var_name];
}
}
set_skin_opts(['wide' => true]);
set_title('$admin_errors');
render('admin/errors',
...$vars);
}
function GET_auth_log() {
$db = DB();
$count = (int)$db->result($db->query("SELECT COUNT(*) FROM admin_log"));
$per_page = 100;
list($page, $pages, $offset) = get_page($per_page, $count);
$q = $db->query("SELECT *,
INET_NTOA(ip) AS ip,
admins.login AS login,
admins.activity_ts AS activitiy_ts
FROM admin_log
LEFT JOIN admins ON admins.id=admin_log.admin_id
ORDER BY admin_log.id DESC
LIMIT $offset, ".$per_page);
$list = $db->fetchAll($q);
if (!empty($list)) {
$list = array_map(function($item) {
$item['date'] = format_time($item['ts']);
$item['activity_ts_s'] = format_time($item['activity_ts']);
return $item;
}, $list);
}
$vars = [
'list' => $list,
'pn_page' => $page,
'pn_pages' => $pages
];
set_skin_opts(['wide' => true]);
set_title('$admin_auth_log');
render('admin/auth_log',
...$vars);
}
function GET_actions_log() {
$field_types = \AdminActions\Util\Logger::getFieldTypes();
foreach ($field_types as $type_prefix => $type_data) {
for ($i = 1; $i <= $type_data['count']; $i++) {
$name = $type_prefix.'arg'.$i;
if (isset($_REQUEST[$name]) && (string)$_REQUEST[$name] !== '')
$argument_filters[$name] = $type_data['unpacker']((string)$_REQUEST[$name]);
}
}
$per_page = 100;
$count = \AdminActions\Util\Logger::getRecordsCount();
list($page, $pages, $offset) = get_page($per_page, $count);
$admin_ids = [];
$admin_logins = [];
$records = \AdminActions\Util\Logger::getRecords($offset, $per_page);
foreach ($records as $record) {
list($admin_id) = $record->getActorInfo();
if ($admin_id !== null)
$admin_ids[$admin_id] = true;
}
if (!empty($admin_ids))
$admin_logins = admin_get_logins_by_id(array_keys($admin_ids));
$url = '/admin/actions-log/?';
$filter_fields = [];
foreach ($field_types as $type_prefix => $type_data) {
for ($i = 1; $i <= $type_data['count']; $i++) {
$name = $type_prefix.'arg'.$i;
$filter_fields[$name] = $argument_filters[$name] ?? '';
}
}
$vars = [
'list' => $records,
'pn_page' => $page,
'pn_pages' => $pages,
'admin_logins' => $admin_logins,
'url' => $url,
'action_types' => \AdminActions\Util\Logger::getActions(true),
];
set_skin_opts(['wide' => true]);
set_title('$admin_actions_log');
render('admin/actions_log',
...$vars);
}
function GET_uploads() {
list($error) = input('error');
$uploads = uploads::getAllUploads();
set_title('$blog_upload');
render('admin/uploads',
error: $error,
uploads: $uploads,
langs: PostLanguage::cases());
}
function POST_uploads() {
csrf_check('addupl');
list($custom_name, $note_en, $note_ru) = input('name, note_en, note_ru');
if (!isset($_FILES['files']))
redirect('/admin/uploads/?error='.urlencode('no file'));
$files = [];
for ($i = 0; $i < count($_FILES['files']['name']); $i++) {
$files[] = [
'name' => $_FILES['files']['name'][$i],
'type' => $_FILES['files']['type'][$i],
'tmp_name' => $_FILES['files']['tmp_name'][$i],
'error' => $_FILES['files']['error'][$i],
'size' => $_FILES['files']['size'][$i],
];
}
if (count($files) > 1) {
$note_en = '';
$note_ru = '';
$custom_name = '';
}
foreach ($files as $f) {
if ($f['error'])
redirect('/admin/uploads/?error='.urlencode('error code '.$f['error']));
if (!$f['size'])
redirect('/admin/uploads/?error='.urlencode('received empty file'));
$ext = extension($f['name']);
if (!uploads::isExtensionAllowed($ext))
redirect('/admin/uploads/?error='.urlencode('extension not allowed'));
$name = $custom_name ?: $f['name'];
$upload_id = uploads::add(
$f['tmp_name'],
$name,
$note_en,
$note_ru);
if (!$upload_id)
redirect('/admin/uploads/?error='.urlencode('failed to create upload'));
admin_log(new \AdminActions\UploadsAdd($upload_id, $name, $note_en, $note_ru));
}
redirect('/admin/uploads/');
}
function GET_upload_delete() {
list($id) = input('i:id');
$upload = uploads::get($id);
if (!$upload)
redirect('/admin/uploads/?error='.urlencode('upload not found'));
csrf_check('delupl'.$id);
uploads::delete($id);
admin_log(new \AdminActions\UploadsDelete($id));
redirect('/admin/uploads/');
}
function POST_upload_edit_note() {
list($id, $note, $lang) = input('i:id, note, lang');
$lang = PostLanguage::tryFrom($lang);
if (!$lang)
not_found();
$upload = uploads::get($id);
if (!$upload)
redirect('/admin/uploads/?error='.urlencode('upload not found'));
csrf_check('editupl'.$id);
$upload->setNote($lang, $note);
$texts = posts::getTextsWithUpload($upload);
if (!empty($texts)) {
foreach ($texts as $text) {
$text->updateHtml();
$text->updateText();
}
}
admin_log(new \AdminActions\UploadsEditNote($id, $note, $lang->value));
redirect('/admin/uploads/');
}
function POST_ajax_md_preview() {
ensure_xhr();
list($md, $title, $use_image_previews, $lang) = input('md, title, b:use_image_previews, lang');
$lang = PostLanguage::tryFrom($lang);
if (!$lang)
$lang = PostLanguage::getDefault();
$html = markup::markdownToHtml($md, $use_image_previews, $lang);
$ctx = skin('admin');
$html = $ctx->markdownPreview(
unsafe_html: $html,
title: $title
);
ajax_ok(['html' => $html]);
}
function GET_page_add() {
list($name) = input('short_name');
$page = pages::getByName($name);
if ($page)
redirect($page->getUrl(), code: HTTPCode::Found);
add_skin_strings_re('/^(err_)?pages_/');
add_skin_strings_re('/^(err_)?blog_/');
set_title(lang('pages_create_title', $name));
static::make_wide();
render('admin/pageForm',
short_name: $name,
title: '',
text: '',
langs: PostLanguage::cases());
}
function POST_page_add() {
csrf_check('addpage');
list($name, $text, $title) = input('short_name, text, title');
$page = pages::getByName($name);
if ($page)
not_found();
$error_code = null;
if (!$title) {
$error_code = 'no_title';
} else if (!$text) {
$error_code = 'no_text';
}
if ($error_code)
ajax_error(['code' => $error_code]);
if (!pages::add([
'short_name' => $name,
'title' => $title,
'md' => $text
])) {
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' => (bool)$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');
static::make_wide();
$js_texts = [];
foreach (PostLanguage::cases() as $pl) {
$js_texts[$pl->value] = [
'title' => '',
'md' => '',
'toc' => false,
];
}
render('admin/postForm',
title: '',
text: '',
langs: PostLanguage::cases(),
short_name: '',
js_texts: $js_texts,
lang: PostLanguage::getDefault()->value);
}
function POST_post_add() {
ensure_xhr();
csrf_check('post_add');
list($visibility_enabled, $short_name, $langs, $date)
= input('b:visible, short_name, langs, date');
self::_postEditValidateCommonData($date);
$lang_data = [];
$at_least_one_lang_is_written = false;
foreach (PostLanguage::cases() as $lang) {
list($title, $text, $toc_enabled) = input("title:{$lang->value}, text:{$lang->value}, b:toc:{$lang->value}", ['trim' => true]);
if ($title !== '' && $text !== '') {
$lang_data[$lang->value] = [$title, $text, $toc_enabled];
$at_least_one_lang_is_written = true;
}
}
$error_code = null;
if (!$at_least_one_lang_is_written) {
$error_code = 'no_text';
} else if (empty($short_name)) {
$error_code = 'no_short_name';
}
if ($error_code)
ajax_error(['code' => $error_code]);
$post = posts::add([
'visible' => $visibility_enabled,
'short_name' => $short_name,
'date' => $date
]);
if (!$post)
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 (!($new_post_text = $post->addText(
lang: PostLanguage::from($lang),
title: $title,
md: $text,
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()]);
}
function GET_post_delete() {
list($name) = input('short_name');
$post = posts::getByName($name);
if (!$post)
not_found();
$id = $post->id;
csrf_check('delpost'.$id);
posts::delete($post);
admin_log(new \AdminActions\PostDelete($id));
redirect('/articles/', code: HTTPCode::Found);
}
function GET_post_edit() {
list($short_name, $saved, $lang) = input('short_name, b:saved, lang');
$lang = PostLanguage::from($lang);
$post = posts::getByName($short_name);
if (!$post)
not_found();
$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
);
}
function POST_post_edit() {
ensure_xhr();
list($old_short_name, $short_name, $langs, $date) = input('short_name, new_short_name, langs, date');
$post = posts::getByName($old_short_name);
if (!$post)
not_found();
csrf_check('editpost'.$post->id);
self::_postEditValidateCommonData($date);
if (empty($short_name))
ajax_error(['code' => 'no_short_name']);
foreach (explode(',', $langs) as $lang) {
$lang = PostLanguage::from($lang);
list($text, $title, $visible, $toc) = input("text:{$lang->value}, title:{$lang->value}, b:visible, b:toc:{$lang->value}");
$error_code = null;
if (!$title)
$error_code = 'no_title';
else if (!$text)
$error_code = 'no_text';
if ($error_code)
ajax_error(['code' => $error_code]);
$pt = $post->getText($lang);
if (!$pt) {
$pt = $post->addText(
lang: $lang,
title: $title,
md: $text,
toc: $toc
);
if (!$pt)
ajax_error(['code' => 'db_err']);
} else {
$pt->edit([
'title' => $title,
'md' => $text,
'toc' => (int)$toc
]);
}
}
$post_data = ['date' => $date, 'visible' => $visible];
if ($post->shortName != $short_name)
$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_books() {
set_title('$admin_books');
render('admin/books');
}
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() {
set_skin_opts([
'full_width' => true,
'no_footer' => true
]);
}
}