470 lines
18 KiB
PHP
Executable File
470 lines
18 KiB
PHP
Executable File
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace mirzaev\ebala\models;
|
||
|
||
// Project files
|
||
use mirzaev\ebala\models\traits\instance,
|
||
mirzaev\ebala\models\traits\status;
|
||
|
||
// Библиотека для ArangoDB
|
||
use ArangoDBClient\Document as _document;
|
||
|
||
// Фреймворк ArangoDB
|
||
use mirzaev\arangodb\collection,
|
||
mirzaev\arangodb\document;
|
||
|
||
// Встроенные библиотеки
|
||
use exception;
|
||
|
||
/**
|
||
* Модель аккаунта
|
||
*
|
||
* @package mirzaev\ebala\models
|
||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||
*/
|
||
final class account extends core
|
||
{
|
||
use instance, status;
|
||
|
||
/**
|
||
* Коллекция
|
||
*/
|
||
final public const COLLECTION = 'account';
|
||
|
||
/**
|
||
* Инстанция документа в базе данных
|
||
*/
|
||
protected readonly _document $document;
|
||
|
||
/**
|
||
* Конструктор
|
||
*
|
||
* @param ?session $session Инстанция сессии
|
||
* @param ?string $authenticate Аутентифицировать аккаунт? Если да, то какой категории? ([worker|operator|market] из $_SERVER['INTERFACE'])
|
||
* @param array &$errors Реестр ошибок
|
||
*
|
||
* @return static Инстанция аккаунта
|
||
*/
|
||
public function __construct(?session $session = null, ?string $authenticate = null, array &$errors = [])
|
||
{
|
||
try {
|
||
if (isset($session)) {
|
||
// Получена инстанция сессии
|
||
|
||
if ($account = $session->account()) {
|
||
// Найден связанный с сессией аккаунт
|
||
|
||
// Инициализация инстанции документа аккаунта в базе данных
|
||
$this->document = $account->document;
|
||
|
||
// Связь сессии с аккаунтом
|
||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||
|
||
return $this;
|
||
} else {
|
||
// Не найден связанный с сессией аккаунт
|
||
if (
|
||
match ($authenticate) {
|
||
'worker', 'operator', 'market', 'administrator' => true,
|
||
default => false
|
||
}
|
||
) {
|
||
// Запрошена аутентификация
|
||
|
||
if (!empty($session->buffer['worker']['entry']['number'])) {
|
||
// Найден номер сотрудника в буфере сессии
|
||
|
||
if (!empty($session->buffer['worker']['entry']['password'])) {
|
||
// Найден пароль в буфере сессии
|
||
|
||
if (($account = self::read('d.number == "' . $session->buffer['worker']['entry']['number'] . '" && d.type == "worker"', amount: 1, errors: $errors)) instanceof _document) {
|
||
// Найден аккаунт сотрудника (игнорируются ошибки)
|
||
|
||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['worker']['entry']['password'])) {
|
||
// Аутентифицирован аккаунт (прошёл проверку пароль)
|
||
|
||
// Инициализация инстанции документа аккаунта в базе данных
|
||
$this->document = $account;
|
||
|
||
// Связь сессии с аккаунтом
|
||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||
|
||
// Удаление использованных данных из буфера сессии
|
||
$session->write(['entry' => ['number' => null, 'password' => null]]);
|
||
|
||
// Выход (успех)
|
||
return $this;
|
||
} else throw new exception('Неправильный пароль');
|
||
|
||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||
} else throw new exception('Не найден аккаунт');
|
||
} else throw new exception('Не найден пароль в буфере сессии');
|
||
} else if (!empty($session->buffer['operator']['entry']['_key'])) {
|
||
// Найден идентификатор оператора в буфере сессии
|
||
|
||
if (!empty($session->buffer['operator']['entry']['password'])) {
|
||
// Найден пароль в буфере сессии
|
||
|
||
if (($account = self::read('d._key == "' . $session->buffer['operator']['entry']['_key'] . '" && d.type == "operator"', amount: 1)) instanceof _document) {
|
||
// Найден аккаунт оператора (игнорируются ошибки)
|
||
|
||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['operator']['entry']['password'])) {
|
||
// Аутентифицирован аккаунт (прошёл проверку пароль)
|
||
|
||
// Инициализация инстанции документа аккаунта в базе данных
|
||
$this->document = $account;
|
||
|
||
// Связь сессии с аккаунтом
|
||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||
|
||
// Удаление использованных данных из буфера сессии
|
||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||
|
||
// Выход (успех)
|
||
return $this;
|
||
} else throw new exception('Неправильный пароль');
|
||
|
||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||
}
|
||
} else throw new exception('Не найден пароль в буфере сессии');
|
||
} else if (!empty($session->buffer['market']['entry']['id'])) {
|
||
// Найден идентификатор магазина в буфере сессии
|
||
|
||
if (!empty($session->buffer['market']['entry']['password'])) {
|
||
// Найден пароль в буфере сессии
|
||
|
||
if (($account = market::account(market::read('d.id == "' . $session->buffer['market']['entry']['id'] . '"', amount: 1)?->getId())) instanceof _document) {
|
||
// Найден аккаунт (игнорируются ошибки)
|
||
|
||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['market']['entry']['password'])) {
|
||
// Аутентифицирован аккаунт (прошёл проверку пароль)
|
||
|
||
// Инициализация инстанции документа аккаунта в базе данных
|
||
$this->document = $account;
|
||
|
||
// Связь сессии с аккаунтом
|
||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||
|
||
// Удаление использованных данных из буфера сессии
|
||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||
|
||
// Выход (успех)
|
||
return $this;
|
||
} else throw new exception('Неправильный пароль');
|
||
|
||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||
}
|
||
} else throw new exception('Не найден пароль в буфере сессии');
|
||
} else if (!empty($session->buffer['administrator']['entry'])) {
|
||
// Найден идентификатор администратора в буфере сессии
|
||
|
||
if (!empty($session->buffer['administrator']['entry']['password'])) {
|
||
// Найден пароль в буфере сессии
|
||
|
||
if (($account = self::read('d._key == "' . $session->buffer['administrator']['entry']['_key'] . '" && d.type == "administrator"', amount: 1)) instanceof _document) {
|
||
// Найден аккаунт администратора (игнорируются ошибки)
|
||
|
||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['administrator']['entry']['password'])) {
|
||
// Аутентифицирован аккаунт (прошёл проверку пароль)
|
||
|
||
// Инициализация инстанции документа аккаунта в базе данных
|
||
$this->document = $account;
|
||
|
||
// Связь сессии с аккаунтом
|
||
session::connect($session->getId(), $this->document->getId(), $errors);
|
||
|
||
// Удаление использованных данных из буфера сессии
|
||
$session->write(['entry' => ['_key' => null, 'password' => null]]);
|
||
|
||
// Выход (успех)
|
||
return $this;
|
||
} else throw new exception('Неправильный пароль');
|
||
|
||
throw new exception('Неизвестная ошибка на этапе проверки пароля');
|
||
}
|
||
} else throw new exception('Не найден пароль в буфере сессии');
|
||
} else throw new exception('Не найдены данные первичной идентификации в буфере сессии');
|
||
}
|
||
}
|
||
} else throw new exception('Не найдена сессия');
|
||
} catch (exception $e) {
|
||
// Запись в реестр ошибок
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Найти связанного с аккаунтом сотрудника
|
||
*
|
||
* @param string $_id Идентификатор аккаунта связанного с сотрудником
|
||
* @param array &$errors Реестр ошибок
|
||
*
|
||
* @return ?_document Инстанция документа сотрудника в базе данных, если найдена
|
||
*/
|
||
public static function worker(string $_id, array &$errors = []): ?_document
|
||
{
|
||
try {
|
||
if (collection::init(static::$arangodb->session, worker::COLLECTION)) {
|
||
// Инициализирована коллекция
|
||
|
||
// Поиск сотрудника
|
||
$worker = collection::search(
|
||
static::$arangodb->session,
|
||
sprintf(
|
||
<<<'AQL'
|
||
FOR d IN %s
|
||
LET e = (
|
||
FOR e IN %s
|
||
FILTER e._from == '%s'
|
||
SORT e.created DESC, e._key DESC
|
||
LIMIT 1
|
||
RETURN e
|
||
)
|
||
FILTER d._id == e[0]._to && d.active == true
|
||
SORT d.created DESC, d._key DESC
|
||
LIMIT 1
|
||
RETURN d
|
||
AQL,
|
||
worker::COLLECTION,
|
||
static::COLLECTION . '_edge_worker',
|
||
$_id
|
||
)
|
||
);
|
||
|
||
// Возврат (успех)
|
||
return $worker instanceof _document ? $worker : throw new exception('Не удалось найти инстанцию сотрудника в базе данных');
|
||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||
} catch (exception $e) {
|
||
// Запись в реестр ошибок
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Найти связанный с аккаунтом магазин
|
||
*
|
||
* @param string $_id Идентификатор аккаунта связанного с магазином
|
||
* @param array &$errors Реестр ошибок
|
||
*
|
||
* @return ?_document Инстанция документа магазина в базе данных, если найдена
|
||
*/
|
||
public static function market(string $_id, array &$errors = []): ?_document
|
||
{
|
||
try {
|
||
if (
|
||
collection::init(static::$arangodb->session, static::COLLECTION)
|
||
&& collection::init(static::$arangodb->session, market::COLLECTION)
|
||
&& collection::init(static::$arangodb->session, static::COLLECTION . '_edge_market', true)
|
||
) {
|
||
// Инициализированы коллекции
|
||
|
||
// Поиск магазина
|
||
$market = collection::search(
|
||
static::$arangodb->session,
|
||
sprintf(
|
||
<<<'AQL'
|
||
FOR d IN %s
|
||
LET e = (
|
||
FOR e IN %s
|
||
FILTER e._from == '%s'
|
||
SORT e.created DESC, e._key DESC
|
||
LIMIT 1
|
||
RETURN e
|
||
)
|
||
FILTER d._id == e[0]._to && d.active == true
|
||
SORT d.created DESC, d.id DESC
|
||
LIMIT 1
|
||
RETURN d
|
||
AQL,
|
||
market::COLLECTION,
|
||
static::COLLECTION . '_edge_market',
|
||
$_id
|
||
)
|
||
);
|
||
|
||
return $market instanceof _document ? $market : throw new exception('Не удалось найти инстанцию магазина в базе данных');
|
||
} else throw new exception('Не удалось инициализировать коллекции');
|
||
} catch (exception $e) {
|
||
// Запись в реестр ошибок
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Инициализировать связь аккаунта с сотрудником
|
||
*
|
||
* Ищет связь аккаунта с сотрудником, если не находит, то создаёт её
|
||
*
|
||
* @param string $account Идентификатор инстанции документа аккаунта в базе данных
|
||
* @param string $target Идентификатор инстанции документа цели в базе данны (подразумевается сотрудник или магазин)
|
||
* @param string $type Тип подключения (worker|market)
|
||
* @param array &$errors Реестр ошибок
|
||
*
|
||
* @return bool Связан аккаунт с сотрудником?
|
||
*/
|
||
public static function connect(string $account, string $target, string $type = 'worker', array &$errors = []): bool
|
||
{
|
||
try {
|
||
if (
|
||
collection::init(static::$arangodb->session, $type)
|
||
&& collection::init(static::$arangodb->session, self::COLLECTION)
|
||
&& collection::init(static::$arangodb->session, self::COLLECTION . "_edge_$type", true)
|
||
) {
|
||
// Инициализированы коллекции
|
||
|
||
if (
|
||
collection::search(static::$arangodb->session, sprintf(
|
||
<<<AQL
|
||
FOR d IN %s
|
||
FILTER d._from == '%s' && d._to == '%s'
|
||
SORT d.created DESC, d._key DESC
|
||
LIMIT 1
|
||
RETURN d
|
||
AQL,
|
||
self::COLLECTION . "_edge_$type",
|
||
$account,
|
||
$target
|
||
)) instanceof _document
|
||
|| document::write(static::$arangodb->session, self::COLLECTION . "_edge_$type", [
|
||
'_from' => $account,
|
||
'_to' => $target
|
||
])
|
||
) {
|
||
// Найдено, либо создано ребро: account -> $type
|
||
|
||
return true;
|
||
} else throw new exception("Не удалось создать ребро: account -> $type");
|
||
} else throw new exception('Не удалось инициализировать коллекцию');
|
||
} catch (exception $e) {
|
||
// Запись в реестр ошибок
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* Создать
|
||
*
|
||
* @param array $data Данные аккаунта
|
||
* @param array &$errors Реестр ошибок
|
||
*
|
||
* @return string|null Идентификатор документа, если он был создан
|
||
*/
|
||
public static function create(array $data = [], array &$errors = []): ?string
|
||
{
|
||
try {
|
||
if (collection::init(static::$arangodb->session, self::COLLECTION))
|
||
if ($id = document::write(static::$arangodb->session, self::COLLECTION, $data + ['active' => true])) return $id;
|
||
else throw new exception('Не удалось создать аккаунт');
|
||
else throw new exception('Не удалось инициализировать коллекцию');
|
||
} catch (exception $e) {
|
||
// Запись в реестр ошибок
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Записать
|
||
*
|
||
* Записывает свойство в инстанцию документа аккаунта из базы данных
|
||
*
|
||
* @param string $name Название
|
||
* @param mixed $value Содержимое
|
||
*
|
||
* @return void
|
||
*/
|
||
public function __set(string $name, mixed $value = null): void
|
||
{
|
||
$this->document->{$name} = $value;
|
||
}
|
||
|
||
/**
|
||
* Прочитать
|
||
*
|
||
* Читает свойство из инстанции документа аккаунта из базы данных
|
||
*
|
||
* @param string $name Название
|
||
*
|
||
* @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных
|
||
*/
|
||
public function __get(string $name): mixed
|
||
{
|
||
return $this->document->{$name};
|
||
}
|
||
|
||
/**
|
||
* Проверить инициализированность
|
||
*
|
||
* Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных
|
||
*
|
||
* @param string $name Название
|
||
*
|
||
* @return bool Свойство инициализировано?
|
||
*/
|
||
public function __isset(string $name): bool
|
||
{
|
||
return isset($this->document->{$name});
|
||
}
|
||
|
||
/**
|
||
* Удалить
|
||
*
|
||
* Деинициализировать свойство в инстанции документа аккаунта из базы данных
|
||
*
|
||
* @param string $name Название
|
||
*
|
||
* @return void
|
||
*/
|
||
public function __unset(string $name): void
|
||
{
|
||
unset($this->document->{$name});
|
||
}
|
||
|
||
/**
|
||
* Выполнить метод
|
||
*
|
||
* Выполнить метод в инстанции документа аккаунта из базы данных
|
||
*
|
||
* @param string $name Название
|
||
* @param array $arguments Аргументы
|
||
*/
|
||
public function __call(string $name, array $arguments = [])
|
||
{
|
||
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
|
||
}
|
||
}
|