This repository has been archived on 2024-10-16. You can view files and clone it, but cannot push or open issues or pull requests.
ebala/mirzaev/ebala/system/controllers/operator.php

298 lines
12 KiB
PHP
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?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();
}
}
}