298 lines
12 KiB
PHP
Executable File
298 lines
12 KiB
PHP
Executable File
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace mirzaev\ebala\controllers;
|
||
|
||
// Файлы проекта
|
||
use mirzaev\ebala\controllers\core,
|
||
mirzaev\ebala\controllers\traits\errors,
|
||
mirzaev\ebala\models\account,
|
||
mirzaev\ebala\models\registry;
|
||
|
||
// Встроенные библиотеки
|
||
use exception;
|
||
|
||
/**
|
||
* Контроллер оператора
|
||
*
|
||
* @package mirzaev\ebala\controllers
|
||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||
*/
|
||
final class operator extends core
|
||
{
|
||
use errors;
|
||
|
||
/**
|
||
* Главная страница
|
||
*
|
||
* @param array $parameters Параметры запроса
|
||
*/
|
||
public function index(array $parameters = []): ?string
|
||
{
|
||
// Авторизация
|
||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||
// Авторизован аккаунт оператора или администратора
|
||
|
||
foreach (['confirmed', 'waiting'] as $name) {
|
||
// Перебор фильтров статусов
|
||
|
||
// Инициализация значения (приоритет у cookie)
|
||
$value = $_COOKIE["operators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0;
|
||
|
||
// Инициализировано значение?
|
||
if ($value === null || $value === 0) continue;
|
||
|
||
// Генерация класса для HTML-элемента по его состоянию (0 - ОТСУТСТВУЕТ, 1 - И, 2 - ИЛИ)
|
||
$this->view->{$name} = match ($value) {
|
||
'0', 0 => 'earth',
|
||
'1', 1 => 'sand',
|
||
'2', 2 => 'river',
|
||
default => 'earth'
|
||
};
|
||
}
|
||
|
||
// Генерация представлениямя
|
||
$main = $this->view->render(DIRECTORY_SEPARATOR . 'pages' . DIRECTORY_SEPARATOR . 'operators.html');
|
||
} else $main = $this->authorization();
|
||
|
||
// Возврат (успех)
|
||
if ($_SERVER['REQUEST_METHOD'] === 'GET') return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', ['main' => $main]);
|
||
else if ($_SERVER['REQUEST_METHOD'] === 'POST') return $main;
|
||
|
||
// Возврат (провал)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Прочитать
|
||
*
|
||
* @param array $parameters Параметры запроса
|
||
*/
|
||
public function read(array $parameters = []): ?string
|
||
{
|
||
if ($this->account->status() && ($this->account->type === 'administrator' || $this->account->type === 'operator')) {
|
||
// Авторизован аккаунт оператора или администратора
|
||
|
||
// Реинициализация актуальной страницы
|
||
if (isset($parameters['page'])) $this->session->write(['operators' => ['page' => $parameters['page']]]);
|
||
else if (empty($this->session->buffer[$_SERVER['INTERFACE']]['operators']['page'])) $this->session->write(['operators' => ['page' => 1]]);
|
||
|
||
// Инициализация буферов AQL-выражений для инъекции фильтра по статусам
|
||
$filters_statuses_and = '';
|
||
$filters_statuses_or = '';
|
||
|
||
foreach (['active', 'inactive'] as $name) {
|
||
// Перебор фильтров статусов (И)
|
||
|
||
// Инициализация значения (приоритет у cookie) (отсутствие значения или значение 0 вызывают continue)
|
||
if (empty($value = $_COOKIE["operators_filter_$name"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters'][$name] ?? 0)) continue;
|
||
|
||
// Конвертация ярлыков
|
||
$converted = match ($name) {
|
||
'inactive' => 'active',
|
||
default => $name
|
||
};
|
||
|
||
// Генерация выражения
|
||
$expression = "account.$converted == " . ($name === $converted ? 'true' : 'false');
|
||
|
||
// Генерация AQL-выражения для инъекции в строку запроса
|
||
if ($value === '1') $filters_statuses_and .= " && " . $expression;
|
||
else if ($value === '2') $filters_statuses_or .= " || " . $expression;
|
||
}
|
||
|
||
// Очистка от бинарных операторов сравнения с только одним операндом (крайние)
|
||
$filters_statuses_and = trim(trim(trim($filters_statuses_and), '&&'));
|
||
$filters_statuses_or = trim(trim(trim($filters_statuses_or), '||'));
|
||
|
||
// Инициализация буфера с объёдинёнными буферами c AQL-выражениям "И" и "ИЛИ"
|
||
$filters_statuses_merged = (empty($filters_statuses_and) ? '' : "($filters_statuses_and)") . (empty($filters_statuses_or) ? '' : (empty($filters_statuses_and) ? '' : ' || ') . "($filters_statuses_or)");
|
||
|
||
// Инициализация общего буфера с AQL-выражениями
|
||
$filters = '';
|
||
|
||
// Объединение фильтров в единую строку с AQL-выражениями для инъекции
|
||
if (!empty($filters_statuses_merged)) $filters .= empty($filters) ? $filters_statuses_merged : " && ($filters_statuses_merged)";
|
||
|
||
// Инициализация строки поиска
|
||
$search = $_COOKIE["operators_filter_search"] ?? @$this->session->buffer[$_SERVER['INTERFACE']]['operators']['filters']['search'] ?? '';
|
||
if (mb_strlen($search) < 3) $search = null;
|
||
$search_query = empty($search)
|
||
? null
|
||
: <<<AQL
|
||
SEARCH
|
||
account.commentary IN TOKENS(@search, 'text_ru')
|
||
|| STARTS_WITH(account._key, @search)
|
||
|| STARTS_WITH(account.name.first, @search)
|
||
|| STARTS_WITH(account.name.second, @search)
|
||
|| STARTS_WITH(account.name.last, @search)
|
||
|| STARTS_WITH(account.number, @search)
|
||
|| STARTS_WITH(account.mail, @search)
|
||
|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(account._key, TOKENS(@search, 'text_en')[0], 2, true))
|
||
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(account.name.first, TOKENS(@search, 'text_ru')[0], 2, true))
|
||
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(account.name.second, TOKENS(@search, 'text_ru')[0], 2, true))
|
||
|| (LENGTH(@search) > 4 && LEVENSHTEIN_MATCH(account.name.last, TOKENS(@search, 'text_ru')[0], 2, true))
|
||
|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(account.number, TOKENS(@search, 'text_en')[0], 2, true))
|
||
|| (LENGTH(@search) > 6 && LEVENSHTEIN_MATCH(account.mail, TOKENS(@search, 'text_en')[0], 2, true))
|
||
AQL;
|
||
|
||
// Инициализация данных для генерации HTML-документа с таблицей
|
||
$this->view->rows = registry::operators(
|
||
before: sprintf(
|
||
<<<AQL
|
||
%s
|
||
FILTER account.type == 'operator' && account.deleted != true%s
|
||
AQL,
|
||
$search_query,
|
||
empty($filters) ? null : " && ($filters)"
|
||
),
|
||
after: <<<AQL
|
||
COLLECT x = account OPTIONS { method: "sorted" }
|
||
AQL,
|
||
page: (int) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page'],
|
||
sort: 'x.created DESC, x._key DESC',
|
||
target: empty($search) ? account::COLLECTION : 'registry_accounts',
|
||
return: '{account: x}',
|
||
binds: empty($search) ? [] : [
|
||
'search' => $search
|
||
]
|
||
);
|
||
|
||
// Запись в cookie (только таким методом можно записать "hostonly: true")
|
||
setcookie(
|
||
'operators_page',
|
||
(string) $this->session->buffer[$_SERVER['INTERFACE']]['operators']['page'],
|
||
[
|
||
'expires' => strtotime('+1 hour'),
|
||
'path' => '/',
|
||
'secure' => true,
|
||
'httponly' => false,
|
||
'samesite' => 'strict'
|
||
]
|
||
);
|
||
|
||
// Запись в глобальную переменную шаблонизатора обрабатываемой страницы
|
||
$this->view->page = $parameters['page'];
|
||
|
||
// Инициализация блока
|
||
return $this->view->render(DIRECTORY_SEPARATOR . 'elements' . DIRECTORY_SEPARATOR . 'operators.html');
|
||
}
|
||
|
||
// Возврат (провал)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Создать
|
||
*
|
||
* @param array $parameters Параметры запроса
|
||
*
|
||
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
|
||
*/
|
||
public function create(array $parameters = []): void
|
||
{
|
||
if ($this->account->status() && $this->account->type === 'administrator') {
|
||
// Авторизован аккаунт администратора
|
||
|
||
// Инициализация буфера ошибок
|
||
$this->errors['account'] ??= [];
|
||
|
||
try {
|
||
// Проверка наличия переданного пароля
|
||
if (!isset($parameters['password'])) throw new exception('Не получен пароль');
|
||
else if (strlen($parameters['password']) < 6) throw new exception('Пароль должен быть не менее 6 символов');
|
||
else if (!empty($parameters['number']) && strlen($parameters['number']) < 11) throw new exception('Несоответствие формату SIM-номера');
|
||
else if (!empty($parameters['mail']) && preg_match('/^.+@.+\.\w+$/', $parameters['mail']) === 0) throw new exception('Несоответствие формату почты');
|
||
|
||
// Универсализация
|
||
/* $parameters['number'] = (int) $parameters['number']; */
|
||
|
||
// Создание аккаунта
|
||
$account = account::create(
|
||
data: [
|
||
'type' => 'operator',
|
||
'name' => [
|
||
'first' => $parameters['name_first'],
|
||
'second' => $parameters['name_second'],
|
||
'last' => $parameters['name_last']
|
||
],
|
||
'number' => $parameters['number'] === 0 ? '' : $parameters['number'],
|
||
'mail' => $parameters['mail'],
|
||
'password' => sodium_crypto_pwhash_str(
|
||
$parameters['password'],
|
||
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
|
||
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
|
||
),
|
||
'commentary' => $parameters['commentary']
|
||
],
|
||
errors: $this->errors['account']
|
||
);
|
||
|
||
// Проверка существования созданного аккаунта
|
||
if (empty($account)) throw new exception('Не удалось создать аккаунт');
|
||
} catch (exception $e) {
|
||
// Write to the errors registry
|
||
$this->errors['operator'][] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
|
||
// Запись заголовков ответа
|
||
header('Content-Type: application/json');
|
||
header('Content-Encoding: none');
|
||
header('X-Accel-Buffering: no');
|
||
|
||
// Инициализация буфера вывода
|
||
ob_start();
|
||
|
||
// Инициализация буфера ответа
|
||
$return = [
|
||
'errors' => self::parse_only_text($this->errors)
|
||
];
|
||
|
||
// Генерация ответа
|
||
echo json_encode($return);
|
||
|
||
// Запись заголовков ответа
|
||
header('Content-Length: ' . ob_get_length());
|
||
|
||
// Отправка и деинициализация буфера вывода
|
||
ob_end_flush();
|
||
flush();
|
||
}
|
||
|
||
// Инициализация идентификатора аккаунта (ключ документа инстанции аккаунта в базе данных)
|
||
$_key = preg_replace('/.+\//', '', $account ?? '');
|
||
|
||
// Запись заголовков ответа
|
||
header('Content-Type: application/json');
|
||
header('Content-Encoding: none');
|
||
header('X-Accel-Buffering: no');
|
||
|
||
// Инициализация буфера вывода
|
||
ob_start();
|
||
|
||
// Генерация ответа
|
||
echo json_encode(
|
||
[
|
||
'clipboard' => empty($this->errors['account']) ? <<<TEXT
|
||
Идентификатор: $_key
|
||
Пароль: {$parameters['password']}
|
||
TEXT : '',
|
||
'errors' => self::parse_only_text($this->errors['account'])
|
||
]
|
||
);
|
||
|
||
// Запись заголовков ответа
|
||
header('Content-Length: ' . ob_get_length());
|
||
|
||
// Отправка и деинициализация буфера вывода
|
||
ob_end_flush();
|
||
flush();
|
||
}
|
||
}
|
||
}
|