Compare commits
No commits in common. "stable" and "1.2.x" have entirely different histories.
|
@ -0,0 +1,74 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\controllers\core,
|
||||||
|
mirzaev\site\account\models\generators\password,
|
||||||
|
mirzaev\site\account\models\invite;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер API
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class api extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Проверить существование
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*
|
||||||
|
* @return string JSON с параметром exist
|
||||||
|
*/
|
||||||
|
public function invite_verify(array $parameters = []): string
|
||||||
|
{
|
||||||
|
// Инициализация буфера ответа
|
||||||
|
$return = ['errors' => &$this->errors];
|
||||||
|
|
||||||
|
// Запрос проверки на существование приглашения
|
||||||
|
$invite = invite::read($parameters['key'], $this->errors['account']);
|
||||||
|
|
||||||
|
$return['exist'] = isset($invite);
|
||||||
|
|
||||||
|
if ($parameters['from'] == 1) $return['from'] = ['login' => 'mirzaev'] ?? $invite->from();
|
||||||
|
|
||||||
|
// Запись заголовка ответа
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
return json_encode($return);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать классический пароль
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*
|
||||||
|
* @return string JSON с параметром password
|
||||||
|
*/
|
||||||
|
public function password_classic(array $parameters = []): string
|
||||||
|
{
|
||||||
|
// Запись заголовка ответа
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
return json_encode(['password' => password::classic((int) $parameters['length'], $this->errors), 'errors' => $this->errors]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать мнемонический пароль
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*
|
||||||
|
* @return string JSON с параметром password
|
||||||
|
*/
|
||||||
|
public function password_mnemonic(array $parameters = []): string
|
||||||
|
{
|
||||||
|
// Запись заголовка ответа
|
||||||
|
header('Content-Type: application/json');
|
||||||
|
|
||||||
|
return json_encode(['password' => password::mnemonic((int) $parameters['length'], $this->errors), 'errors' => $this->errors]);
|
||||||
|
}
|
||||||
|
}
|
|
@ -6,7 +6,18 @@ namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
// Файлы проекта
|
// Файлы проекта
|
||||||
use mirzaev\site\account\controllers\core,
|
use mirzaev\site\account\controllers\core,
|
||||||
mirzaev\site\account\models\account as model;
|
mirzaev\site\account\models\account as model,
|
||||||
|
mirzaev\site\account\models\session,
|
||||||
|
mirzaev\site\account\models\vk;
|
||||||
|
|
||||||
|
// Фреймворк для ВКонтакте
|
||||||
|
use mirzaev\vk\core as api;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Контроллер аккаунта
|
* Контроллер аккаунта
|
||||||
|
@ -25,4 +36,114 @@ final class account extends core
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function initialization(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
if ($this->variables['account'] instanceof _document) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
if ($this->variables['vk'] instanceof _document) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
vk::parse($this->variables['vk'], $this->variables['errors']['vk']);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(200);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
// Не найден аккаунт
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(401);
|
||||||
|
|
||||||
|
// Запись заголовка ответа с ключом аккаунта
|
||||||
|
header('session: ' . $this->variables['session']->hash);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись кода ответа
|
||||||
|
http_response_code(500);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Связь аккаунта с аккаунтом ВКонтакте
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function connect(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
if ($this->variables['session']->hash === $parameters['state']) {
|
||||||
|
// Совпадает хеш сессии с полученным хешем из ответа ВКонтакте
|
||||||
|
|
||||||
|
if (!empty($response = vk::key($parameters['code'], $this->variables['errors']['vk']))) {
|
||||||
|
// Получены данные аккаунта ВКонтакте
|
||||||
|
|
||||||
|
if (($this->variables['vk'] = vk::initialization($response, $this->variables['errors']['vk'])) instanceof _document) {
|
||||||
|
// Инициализирован аккаунт ВКонтакте
|
||||||
|
|
||||||
|
if (($this->variables['account'] = vk::account($this->variables['vk'])) instanceof _document) {
|
||||||
|
// Найден аккаунт (существующий)
|
||||||
|
|
||||||
|
if (session::connect($this->variables['session'], $this->variables['account'], $this->variables['errors']['session'])) {
|
||||||
|
// Связана сессия с аккаунтом
|
||||||
|
}
|
||||||
|
} else if (($this->variables['account'] = model::create($this->variables['errors']['account'])) instanceof _document) {
|
||||||
|
// Найден аккаунт (создан новый)
|
||||||
|
|
||||||
|
if (session::connect($this->variables['session'], $this->variables['account'], $this->variables['errors']['session'])) {
|
||||||
|
// Связана сессия с аккаунтом
|
||||||
|
|
||||||
|
if (account::connect($this->variables['account'], $this->variables['vk'], $this->variables['errors']['account'])) {
|
||||||
|
// Связан аккаунт с аккаунтом ВКонтакте
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация робота для аккаунта ВКонтакте
|
||||||
|
$this->vk = api::init()->user(key: $this->variables['vk']->access['key']);
|
||||||
|
|
||||||
|
if ($this->variables['vk'] instanceof _document) {
|
||||||
|
// Инициализирован робот для аккаунта ВКонтакте
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
$data = vk::parse($this->vk, $this->variables['errors']['vk']);
|
||||||
|
var_dump($data);
|
||||||
|
die;
|
||||||
|
|
||||||
|
if ($data instanceof stdClass) {
|
||||||
|
// Получены данные ВКонтакте
|
||||||
|
|
||||||
|
// Запись в базу данных
|
||||||
|
vk::update($this->variables['vk'], $data, $this->variables['errors']['vk']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'account' . DIRECTORY_SEPARATOR . 'vk.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Генерация панели аккаунта
|
||||||
|
*
|
||||||
|
* @param array $parameters Параметры запроса
|
||||||
|
*/
|
||||||
|
public function panel(array $parameters = []): ?string
|
||||||
|
{
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'account' . DIRECTORY_SEPARATOR . 'panel.html', $this->variables);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,7 +53,7 @@ final class api extends core
|
||||||
default => throw new exception("Параметр не найден: $parameter")
|
default => throw new exception("Параметр не найден: $parameter")
|
||||||
};
|
};
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$this->errors[] = [
|
$this->errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
|
|
@ -5,16 +5,20 @@ declare(strict_types=1);
|
||||||
namespace mirzaev\site\account\controllers;
|
namespace mirzaev\site\account\controllers;
|
||||||
|
|
||||||
// Файлы проекта
|
// Файлы проекта
|
||||||
use mirzaev\site\account\views\templater,
|
use mirzaev\site\account\views\templater;
|
||||||
mirzaev\site\account\models\core as models,
|
use mirzaev\site\account\models\core as models;
|
||||||
mirzaev\site\account\models\account,
|
use mirzaev\site\account\models\account;
|
||||||
mirzaev\site\account\models\session;
|
use mirzaev\site\account\models\session;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
// Фреймворк PHP
|
// Фреймворк PHP
|
||||||
use mirzaev\minimal\controller;
|
use mirzaev\minimal\controller;
|
||||||
|
|
||||||
// Встроенные библиотеки
|
// Фреймворк ВКонтакте
|
||||||
use exception;
|
use mirzaev\vk\core as vk;
|
||||||
|
use mirzaev\vk\robots\user as robot;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ядро контроллеров
|
* Ядро контроллеров
|
||||||
|
@ -24,6 +28,11 @@ use exception;
|
||||||
*/
|
*/
|
||||||
class core extends controller
|
class core extends controller
|
||||||
{
|
{
|
||||||
|
/**
|
||||||
|
* Переменные окружения
|
||||||
|
*/
|
||||||
|
protected robot $vk;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инстанция сессии
|
* Инстанция сессии
|
||||||
*/
|
*/
|
||||||
|
@ -50,46 +59,45 @@ class core extends controller
|
||||||
/**
|
/**
|
||||||
* Конструктор
|
* Конструктор
|
||||||
*
|
*
|
||||||
* @param bool $initialize Инициализировать контроллер?
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(bool $initialize = true)
|
public function __construct()
|
||||||
{
|
{
|
||||||
parent::__construct($initialize);
|
parent::__construct();
|
||||||
|
|
||||||
if ($initialize) {
|
// Инициализация ядра моделей (соединение с базой данных...)
|
||||||
// Запрошена инициализация
|
new models();
|
||||||
|
|
||||||
// Инициализация ядра моделей (соединение с базой данных...)
|
// Инициализация шаблонизатора представлений
|
||||||
new models();
|
$this->view = new templater;
|
||||||
|
|
||||||
// Инициализация даты до которой будет активна сессия
|
// Инициализация даты до которой будет активна сессия
|
||||||
$expires = time() + 604800;
|
$expires = time() + 604800;
|
||||||
|
|
||||||
// Инициализация значения по умолчанию
|
// Инициализация сессии (без журналирования)
|
||||||
$_COOKIE["session"] ??= null;
|
$this->session = new session($_COOKIE["session"] ?? null, $expires) ??
|
||||||
|
header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию');
|
||||||
|
|
||||||
// Инициализация сессии
|
if ($_COOKIE["session"] ?? null !== $this->session->hash) {
|
||||||
$this->session = new session($_COOKIE["session"], $expires);
|
// Изменился хеш сессии (подразумевается, что сессия устарела)
|
||||||
|
|
||||||
if ($_COOKIE["session"] !== $this->session->hash) {
|
// Запись хеша новой сессии
|
||||||
// Изменился хеш сессии (подразумевается, что сессия устарела)
|
setcookie('session', $this->session->hash, [
|
||||||
|
'expires' => $expires,
|
||||||
|
'domain' => 'mirzaev.sexy',
|
||||||
|
'path' => '/',
|
||||||
|
'secure' => true,
|
||||||
|
'httponly' => true,
|
||||||
|
'samesite' => 'strict'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
// Запись хеша новой сессии
|
// Инициализация аккаунта (без журналирования)
|
||||||
setcookie('session', $this->session->hash, [
|
$this->account = $this->session->account();
|
||||||
'expires' => $expires,
|
|
||||||
'domain' => 'mirzaev.sexy',
|
|
||||||
'path' => '/',
|
|
||||||
'secure' => true,
|
|
||||||
'httponly' => true,
|
|
||||||
'samesite' => 'strict'
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация аккаунта
|
if ($this->account instanceof _document) {
|
||||||
$this->account = new account($this->session);
|
// Инициализирован аккаунт
|
||||||
|
|
||||||
// Инициализация шаблонизатора представлений
|
|
||||||
$this->view = new templater($this->session, $this->account);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,65 +15,68 @@ use mirzaev\site\account\controllers\core;
|
||||||
*/
|
*/
|
||||||
final class hotline extends core
|
final class hotline extends core
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
* Страница с бегущей строкой
|
* Страница с бегущей строкой
|
||||||
*
|
*
|
||||||
* Можно использовать совместно с элементом <iframe> для изоляции
|
* Можно использовать совместно с элементом <iframe> для изоляции
|
||||||
* содержимого бегущей строки от поисковых роботов
|
* содержимого бегущей строки от поисковых роботов
|
||||||
*
|
*
|
||||||
* @param array $parameters
|
* @param array $parameters
|
||||||
*/
|
*/
|
||||||
public function index(array $parameters = []): ?string
|
public function index(array $parameters = []): ?string
|
||||||
{
|
{
|
||||||
// Инициализация элементов для генерации в головном элементе
|
// Инициализация элементов для генерации в головном элементе
|
||||||
$this->variables['head'] = [
|
$this->variables['head'] = [
|
||||||
'title' => 'Бегущая строка',
|
'title' => 'Бегущая строка',
|
||||||
'metas' => [
|
'metas' => [
|
||||||
[
|
[
|
||||||
'attributes' => [
|
'attributes' => [
|
||||||
'name' => 'robots',
|
'name' => 'robots',
|
||||||
'content' => 'nofollow'
|
'content' => 'nofollow'
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|
||||||
// Инициализация бегущей строки
|
// Инициализация бегущей строки
|
||||||
$this->variables['hotline'] = [
|
$this->variables['hotline'] = [
|
||||||
'id' => $this->variables['request']['id'] ?? 'hotline'
|
'id' => $this->variables['request']['id'] ?? 'hotline'
|
||||||
];
|
];
|
||||||
|
|
||||||
// Инициализация параметров бегущей строки
|
// Инициализация параметров бегущей строки
|
||||||
$this->variables['hotline']['parameters'] = [
|
$this->variables['hotline']['parameters'] = [
|
||||||
// 'step' => 2
|
// 'step' => 2
|
||||||
];
|
];
|
||||||
|
|
||||||
// Инициализация аттрибутов бегущей строки
|
// Инициализация аттрибутов бегущей строки
|
||||||
$this->variables['hotline']['attributes'] = [];
|
$this->variables['hotline']['attributes'] = [
|
||||||
|
|
||||||
// Инициализация элементов бегущей строки
|
];
|
||||||
$this->variables['hotline']['elements'] = [
|
|
||||||
['content' => '1'],
|
// Инициализация элементов бегущей строки
|
||||||
[
|
$this->variables['hotline']['elements'] = [
|
||||||
'tag' => 'article',
|
['content' => '1'],
|
||||||
'content' => '2'
|
[
|
||||||
],
|
'tag' => 'article',
|
||||||
['content' => '3'],
|
'content' => '2'
|
||||||
['content' => '4'],
|
],
|
||||||
['content' => '5'],
|
['content' => '3'],
|
||||||
['content' => '6'],
|
['content' => '4'],
|
||||||
['content' => '7'],
|
['content' => '5'],
|
||||||
['content' => '8'],
|
['content' => '6'],
|
||||||
['content' => '9'],
|
['content' => '7'],
|
||||||
['content' => '10'],
|
['content' => '8'],
|
||||||
['content' => '11'],
|
['content' => '9'],
|
||||||
['content' => '12'],
|
['content' => '10'],
|
||||||
['content' => '13'],
|
['content' => '11'],
|
||||||
['content' => '14'],
|
['content' => '12'],
|
||||||
['content' => '15']
|
['content' => '13'],
|
||||||
];
|
['content' => '14'],
|
||||||
|
['content' => '15']
|
||||||
|
];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
||||||
|
}
|
||||||
|
|
||||||
// Генерация представления
|
|
||||||
return $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'index.html', $this->variables);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,9 +24,7 @@ final class index extends core
|
||||||
{
|
{
|
||||||
// Инициализация узлов
|
// Инициализация узлов
|
||||||
$this->view->nodes = [
|
$this->view->nodes = [
|
||||||
'account' => $this->view->render(DIRECTORY_SEPARATOR . (isset($this->account->document)
|
'account' => $this->view->render(DIRECTORY_SEPARATOR . 'nodes' . DIRECTORY_SEPARATOR . (isset($this->account) ? 'profile.html' : 'authentication.html'))
|
||||||
? 'nodes' . DIRECTORY_SEPARATOR . 'profile.html'
|
|
||||||
: 'pages' . DIRECTORY_SEPARATOR . 'entry.html'))
|
|
||||||
/* 'account' => $this->view->render(DIRECTORY_SEPARATOR . 'nodes' . DIRECTORY_SEPARATOR . (isset($this->account) ? 'profile.html' : 'connect.html')) */
|
/* 'account' => $this->view->render(DIRECTORY_SEPARATOR . 'nodes' . DIRECTORY_SEPARATOR . (isset($this->account) ? 'profile.html' : 'connect.html')) */
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -24,16 +24,16 @@ final class session extends core
|
||||||
use errors;
|
use errors;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записать входной псевдоним в буфер сессии
|
* Записать входной псевдоним
|
||||||
*
|
*
|
||||||
* Проверяет существование аккаунта с этим входным псевдонимом
|
* Проверяет существование аккаунта с этим входным псевдонимом
|
||||||
* и запоминает для использования в процессе аутентификации
|
* и запоминает для использования в процессе аутентификации
|
||||||
*
|
*
|
||||||
* @param array $parameters Параметры запроса
|
* @param array $parameters Параметры запроса
|
||||||
*
|
*
|
||||||
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
|
* @return string JSON-документ с запрашиваемыми параметрами
|
||||||
*/
|
*/
|
||||||
public function login(array $parameters = []): void
|
public function login(array $parameters = []): string
|
||||||
{
|
{
|
||||||
// Инициализация буфера ответа
|
// Инициализация буфера ответа
|
||||||
$buffer = [];
|
$buffer = [];
|
||||||
|
@ -51,27 +51,20 @@ final class session extends core
|
||||||
// Проверка параметров на соответствование требованиям
|
// Проверка параметров на соответствование требованиям
|
||||||
if ($length === 0) throw new exception('Входной псевдоним не может быть пустым');
|
if ($length === 0) throw new exception('Входной псевдоним не может быть пустым');
|
||||||
if ($length > 100) throw new exception('Входной псевдоним не может быть длиннее 100 символов');
|
if ($length > 100) throw new exception('Входной псевдоним не может быть длиннее 100 символов');
|
||||||
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['login'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
|
||||||
|
|
||||||
// Поиск аккаунта
|
// Поиск аккаунта
|
||||||
$account = account::login($parameters['login']);
|
$account = account::read($parameters['login'], $this->errors['account']);
|
||||||
|
|
||||||
// Генерация ответа по запрашиваемым параметрам
|
// Генерация ответа по запрашиваемым параметрам
|
||||||
foreach ($return as $parameter) match ($parameter) {
|
foreach ($return as $parameter) match ($parameter) {
|
||||||
'exist' => $buffer['exist'] = isset($account->document),
|
'exist' => $buffer['exist'] = isset($account->instance),
|
||||||
'account' => (function () use ($parameters, &$buffer) {
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['login' => $parameters['login']]], $this->errors);
|
|
||||||
|
|
||||||
// Поиск аккаунта и запись в буфер вывода
|
|
||||||
$buffer['account'] = isset((new account($this->session, authenticate: true, errors: $this->errors))->document);
|
|
||||||
})(),
|
|
||||||
'errors' => null,
|
'errors' => null,
|
||||||
default => throw new exception("Параметр не найден: $parameter")
|
default => throw new exception("Параметр не найден: $parameter")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ($parameters['remember'] === '1') $this->session->remember('account.identification.login', $parameters['login']);
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$this->errors['session'][] = [
|
$this->errors['session'][] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -83,40 +76,23 @@ final class session extends core
|
||||||
// Запись реестра ошибок в буфер ответа
|
// Запись реестра ошибок в буфер ответа
|
||||||
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
||||||
|
|
||||||
// Запись заголовков ответа
|
// Запись заголовка ответа
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header('Content-Encoding: none');
|
|
||||||
header('X-Accel-Buffering: no');
|
|
||||||
|
|
||||||
// Инициализация буфера вывода
|
return json_encode($buffer);
|
||||||
ob_start();
|
|
||||||
|
|
||||||
// Генерация ответа
|
|
||||||
echo json_encode($buffer);
|
|
||||||
|
|
||||||
// Запись заголовков ответа
|
|
||||||
header('Content-Length: ' . ob_get_length());
|
|
||||||
|
|
||||||
// Отправка и деинициализация буфера вывода
|
|
||||||
ob_end_flush();
|
|
||||||
flush();
|
|
||||||
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['login' => $parameters['login']]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записать пароль в буфер сессии
|
* Записать пароль
|
||||||
*
|
*
|
||||||
* Проверяет на соответствие требованиям
|
* Проверяет на соответствие требованиям
|
||||||
* и запоминает для использования в процессе аутентификации
|
* и запоминает для использования в процессе аутентификации
|
||||||
*
|
*
|
||||||
* @param array $parameters Параметры запроса
|
* @param array $parameters Параметры запроса
|
||||||
*
|
*
|
||||||
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
|
* @return string JSON-документ с запрашиваемыми параметрами
|
||||||
*/
|
*/
|
||||||
public function password(array $parameters = []): void
|
public function password(array $parameters = []): string
|
||||||
{
|
{
|
||||||
// Инициализация буфера ответа
|
// Инициализация буфера ответа
|
||||||
$buffer = [];
|
$buffer = [];
|
||||||
|
@ -125,74 +101,57 @@ final class session extends core
|
||||||
$return = explode(',', $parameters['return'], 50);
|
$return = explode(',', $parameters['return'], 50);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Проверка наличия обязательных параметров
|
||||||
|
if (empty($parameters['password'])) throw new exception('Необходимо передать пароль');
|
||||||
|
|
||||||
// Вычисление длины
|
// Вычисление длины
|
||||||
$length = strlen($parameters['password']);
|
$length = strlen($parameters['password']);
|
||||||
|
|
||||||
// Проверка параметров на соответствование требованиям
|
// Проверка параметров на соответствование требованиям
|
||||||
|
if ($length === 0) throw new exception('Пароль не может быть пустым');
|
||||||
if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов');
|
if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов');
|
||||||
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
|
||||||
|
|
||||||
// Генерация ответа по запрашиваемым параметрам
|
// Генерация ответа по запрашиваемым параметрам
|
||||||
foreach ($return as $parameter) match ($parameter) {
|
foreach ($return as $parameter) match ($parameter) {
|
||||||
'verify' => $buffer['verify'] = true,
|
'verify' => $buffer['verify'] = true,
|
||||||
'account' => (function() use ($parameters, &$buffer) {
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['password' => $parameters['password']]], $this->errors);
|
|
||||||
|
|
||||||
// Поиск аккаунта и запись в буфер вывода
|
|
||||||
$buffer['account'] = isset((new account($this->session, authenticate: true, register: true, errors: $this->errors))->document);
|
|
||||||
})(),
|
|
||||||
'errors' => null,
|
'errors' => null,
|
||||||
default => throw new exception("Параметр не найден: $parameter")
|
default => throw new exception("Параметр не найден: $parameter")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ($parameters['remember'] === '1') throw new exception('Запоминать пароль не безопасно');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$this->errors['session'][] = [
|
$this->errors['session'][] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
'line' => $e->getLine(),
|
'line' => $e->getLine(),
|
||||||
'stack' => $e->getTrace()
|
'stack' => $e->getTrace()
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// Запись реестра ошибок в буфер ответа
|
||||||
|
if (in_array('verify', $return, true)) $buffer['verify'] = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Запись реестра ошибок в буфер ответа
|
// Запись реестра ошибок в буфер ответа
|
||||||
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
||||||
|
|
||||||
// Запись заголовков ответа
|
// Запись заголовка ответа
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header('Content-Encoding: none');
|
|
||||||
header('X-Accel-Buffering: no');
|
|
||||||
|
|
||||||
// Инициализация буфера вывода
|
return json_encode($buffer);
|
||||||
ob_start();
|
|
||||||
|
|
||||||
// Генерация ответа
|
|
||||||
echo json_encode($buffer);
|
|
||||||
|
|
||||||
// Запись заголовков ответа
|
|
||||||
header('Content-Length: ' . ob_get_length());
|
|
||||||
|
|
||||||
// Отправка и деинициализация буфера вывода
|
|
||||||
ob_end_flush();
|
|
||||||
flush();
|
|
||||||
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['password' => $parameters['password']]]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записать код приглашения в буфер сессии
|
* Записать код приглашения
|
||||||
*
|
*
|
||||||
* Проверяет существование приглашения с этим кодом
|
* Проверяет существование приглашения с этим кодом
|
||||||
* и запоминает для использования в процессе регистрации
|
* и запоминает для использования в процессе регистрации
|
||||||
*
|
*
|
||||||
* @param array $parameters Параметры запроса
|
* @param array $parameters Параметры запроса
|
||||||
*
|
*
|
||||||
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
|
* @return string JSON-документ с запрашиваемыми параметрами
|
||||||
*/
|
*/
|
||||||
public function invite(array $parameters = []): void
|
public function invite(array $parameters = []): string
|
||||||
{
|
{
|
||||||
// Инициализация буфера ответа
|
// Инициализация буфера ответа
|
||||||
$buffer = [];
|
$buffer = [];
|
||||||
|
@ -209,29 +168,22 @@ final class session extends core
|
||||||
|
|
||||||
// Проверка параметров на соответствование требованиям
|
// Проверка параметров на соответствование требованиям
|
||||||
if ($length === 0) throw new exception('Получен пустой ключ приглашения');
|
if ($length === 0) throw new exception('Получен пустой ключ приглашения');
|
||||||
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['invite'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
|
|
||||||
|
|
||||||
// Поиск приглашения
|
// Поиск приглашения
|
||||||
$invite = invite::read($parameters['invite']);
|
$invite = invite::read($parameters['invite'], $this->errors['session']);
|
||||||
|
|
||||||
// Генерация ответа по запрашиваемым параметрам
|
// Генерация ответа по запрашиваемым параметрам
|
||||||
foreach ($return as $parameter) match ($parameter) {
|
foreach ($return as $parameter) match ($parameter) {
|
||||||
'exist' => $buffer['exist'] = isset($invite->document),
|
'exist' => $buffer['exist'] = isset($invite->instance),
|
||||||
// from временное решение пока не будет разработана система сессий
|
// from временное решение пока не будет разработана система сессий
|
||||||
'from' => $buffer['from'] = ['login' => 'mirzaev'] ?? $invite->from(),
|
'from' => $return['from'] = ['login' => 'mirzaev'] ?? $invite->from(),
|
||||||
'account' => (function () use ($parameters, &$buffer) {
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['invite' => $parameters['invite']]], $this->errors);
|
|
||||||
|
|
||||||
// Поиск аккаунта и запись в буфер вывода
|
|
||||||
$buffer['account'] = isset((new account($this->session, authenticate: true, errors: $this->errors))->document);
|
|
||||||
})(),
|
|
||||||
'errors' => null,
|
'errors' => null,
|
||||||
default => throw new exception("Параметр не найден: $parameter")
|
default => throw new exception("Параметр не найден: $parameter")
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if ($parameters['remember'] === '1') $this->session->remember('account.registration.invite', $parameters['invite']);
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$this->errors['session'][] = [
|
$this->errors['session'][] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -243,26 +195,9 @@ final class session extends core
|
||||||
// Запись реестра ошибок в буфер ответа
|
// Запись реестра ошибок в буфер ответа
|
||||||
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
|
||||||
|
|
||||||
// Запись заголовков ответа
|
// Запись заголовка ответа
|
||||||
header('Content-Type: application/json');
|
header('Content-Type: application/json');
|
||||||
header('Content-Encoding: none');
|
|
||||||
header('X-Accel-Buffering: no');
|
|
||||||
|
|
||||||
// Инициализация буфера вывода
|
return json_encode($buffer);
|
||||||
ob_start();
|
|
||||||
|
|
||||||
// Генерация ответа
|
|
||||||
echo json_encode($buffer);
|
|
||||||
|
|
||||||
// Запись заголовков ответа
|
|
||||||
header('Content-Length: ' . ob_get_length());
|
|
||||||
|
|
||||||
// Отправка и деинициализация буфера вывода
|
|
||||||
ob_end_flush();
|
|
||||||
flush();
|
|
||||||
|
|
||||||
// Запись в буфер сессии
|
|
||||||
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
|
|
||||||
$this->session->write(['entry' => ['invite' => $parameters['invite']]]);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ trait errors
|
||||||
|
|
||||||
// Проверка на вложенность и запись в буфер вывода (вход в рекурсию)
|
// Проверка на вложенность и запись в буфер вывода (вход в рекурсию)
|
||||||
if (isset($error['text'])) $buffer[] = $error['text'];
|
if (isset($error['text'])) $buffer[] = $error['text'];
|
||||||
else if (is_array($error) && count($error) > 0) $buffer[$offset] = static::parse_only_text($error);
|
else if (is_array($error)) $buffer[$offset] = static::parse_only_text($error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return $buffer;
|
return $buffer;
|
||||||
|
|
|
@ -4,6 +4,9 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace mirzaev\site\account\models;
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\models\vk;
|
||||||
|
|
||||||
// Фреймворк ArangoDB
|
// Фреймворк ArangoDB
|
||||||
use mirzaev\arangodb\collection,
|
use mirzaev\arangodb\collection,
|
||||||
mirzaev\arangodb\document;
|
mirzaev\arangodb\document;
|
||||||
|
@ -28,162 +31,19 @@ final class account extends core
|
||||||
public const COLLECTION = 'account';
|
public const COLLECTION = 'account';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инстанция документа аккаунта в базе данных
|
* Инстанция в базе данных
|
||||||
*/
|
*/
|
||||||
public ?_document $document;
|
public ?_document $instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Конструктор
|
* Прочитать
|
||||||
*
|
|
||||||
* 1. Проверяет связь сессии с аккаунтом
|
|
||||||
* 1.1. Если найдена связь, то возвращает связанный аккаунт (выход)
|
|
||||||
* 2. [authenticate === true] Проверяет наличие данных в буфере сессии
|
|
||||||
* 2.1 Если найден входной псевдоним и пароли совпадают, то аутентифицирует (выход)
|
|
||||||
* 2.2 [register === true] Если найдены данные для регистрации, то регистрирует (выход)
|
|
||||||
*
|
|
||||||
* @param ?session $session Инстанция сессии
|
|
||||||
* @param bool $authenticate Аутентифицировать аккаунт?
|
|
||||||
* @param bool $register Регистрировать аккаунт?
|
|
||||||
* @param array &$errors Реестр ошибок
|
|
||||||
*
|
|
||||||
* @return static Инстанция аккаунта
|
|
||||||
*/
|
|
||||||
public function __construct(?session $session = null, bool $authenticate = false, bool $register = false, array &$errors = [])
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (isset($session)) {
|
|
||||||
// Получена инстанция сессии
|
|
||||||
|
|
||||||
if ($account = $session->account()) {
|
|
||||||
// Найден связанный с сессией аккаунт
|
|
||||||
|
|
||||||
// Инициализация инстанции документа аккаунта в базе данных
|
|
||||||
$this->document = $account->document;
|
|
||||||
|
|
||||||
// Связь сессии с аккаунтом
|
|
||||||
$session->connect($this, $errors);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
} else {
|
|
||||||
// Не найден связанный с сессией аккаунт
|
|
||||||
|
|
||||||
if ($authenticate) {
|
|
||||||
// Запрошена аутентификация
|
|
||||||
|
|
||||||
if (!empty($session->buffer['entry'])) {
|
|
||||||
// Найдены данные для идентификации в буфере сессии
|
|
||||||
|
|
||||||
if (!empty($session->buffer['entry']['login'])) {
|
|
||||||
// Найдены входной псевдоним в буфере сессии
|
|
||||||
|
|
||||||
if (($account = self::login($session->buffer['entry']['login'])) instanceof self) {
|
|
||||||
// Найден аккаунт (игнорируются ошибки)
|
|
||||||
|
|
||||||
if (isset($account->password) && $account->password === '') {
|
|
||||||
// Не имеет пароля аккаунт
|
|
||||||
|
|
||||||
// Проверка отсутствия переданного пароля
|
|
||||||
if (isset($session->buffer['entry']['password']) && $session->buffer['entry']['password'] !== '') throw new exception('Неправильный пароль');
|
|
||||||
|
|
||||||
// Инициализация инстанции документа аккаунта в базе данных
|
|
||||||
$this->document = $account->document;
|
|
||||||
|
|
||||||
// Связь сессии с аккаунтом
|
|
||||||
$session->connect($this, $errors);
|
|
||||||
|
|
||||||
// Удаление использованных данных из буфера сессии
|
|
||||||
$session->write(['entry' => ['password' => null]]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
} else if (!empty($session->buffer['entry']['password'])) {
|
|
||||||
// Найден пароль в буфере сессии
|
|
||||||
|
|
||||||
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['entry']['password'])) {
|
|
||||||
// Аутентифицирован аккаунт (прошёл проверку пароль, либо аккаунт не имеет пароля)
|
|
||||||
|
|
||||||
// Инициализация инстанции документа аккаунта в базе данных
|
|
||||||
$this->document = $account->document;
|
|
||||||
|
|
||||||
// Связь сессии с аккаунтом
|
|
||||||
$session->connect($this, $errors);
|
|
||||||
|
|
||||||
// Удаление использованных данных из буфера сессии
|
|
||||||
$session->write(['entry' => ['password' => null]]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
} else throw new exception('Неправильный пароль');
|
|
||||||
} throw new exception('Неправильный пароль');
|
|
||||||
} else {
|
|
||||||
// Не найден аккаунт
|
|
||||||
|
|
||||||
if ($register) {
|
|
||||||
// Запрошена регистрация
|
|
||||||
|
|
||||||
if (!empty($session->buffer['entry']['invite'])) {
|
|
||||||
// Найден ключ приглашения в буфере сессии
|
|
||||||
|
|
||||||
// Проверка наличия переданного пароля
|
|
||||||
if (!isset($session->buffer['entry']['password'])) throw new exception('Не найден пароль в буфере сессии');
|
|
||||||
|
|
||||||
if (self::create(
|
|
||||||
[
|
|
||||||
'login' => $session->buffer['entry']['login'],
|
|
||||||
'password' => $session->buffer['entry']['password'] === ''
|
|
||||||
? ''
|
|
||||||
: sodium_crypto_pwhash_str(
|
|
||||||
$session->buffer['entry']['password'],
|
|
||||||
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
|
|
||||||
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
|
|
||||||
)
|
|
||||||
],
|
|
||||||
$errors
|
|
||||||
)) {
|
|
||||||
// Зарегистрирован аккаунт
|
|
||||||
|
|
||||||
if (($account = self::login($session->buffer['entry']['login'], $errors)) instanceof self) {
|
|
||||||
// Найден аккаунт
|
|
||||||
|
|
||||||
// Инициализация инстанции документа аккаунта в базе данных
|
|
||||||
$this->document = $account->document;
|
|
||||||
|
|
||||||
// Связь сессии с аккаунтом
|
|
||||||
$session->connect($this, $errors);
|
|
||||||
|
|
||||||
// Удаление использованных данных из буфера сессии
|
|
||||||
$session->write(['entry' => ['password' => null, 'invite' => null]]);
|
|
||||||
|
|
||||||
return $this;
|
|
||||||
} else throw new exception('Не удалось аутентифицировать аккаунт после его регистрации');
|
|
||||||
} else 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 $login Входной псевдоним
|
* @param string $login Входной псевдоним
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return ?self Инстанция аккаунта, если аутентифицирован
|
* @return ?self Инстанция аккаунта, если найден
|
||||||
*/
|
*/
|
||||||
public static function login(string $login, array &$errors = []): ?self
|
public static function read(string $login, array &$errors = []): ?self
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (collection::init(static::$db->session, self::COLLECTION)) {
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
@ -192,25 +52,26 @@ final class account extends core
|
||||||
// Инициализация инстанции аккаунта
|
// Инициализация инстанции аккаунта
|
||||||
$instance = new self;
|
$instance = new self;
|
||||||
|
|
||||||
// Поиск инстанции аккаунта в базе данных
|
// Поиск аккаунта
|
||||||
$instance->document = collection::search(
|
$instance->instance = collection::search(
|
||||||
static::$db->session,
|
static::$db->session,
|
||||||
sprintf(
|
sprintf(
|
||||||
<<<'AQL'
|
<<<AQL
|
||||||
FOR d IN %s
|
FOR d IN %s
|
||||||
FILTER d.login == '%s'
|
FILTER d.login == '%s'
|
||||||
RETURN d
|
RETURN d
|
||||||
AQL,
|
AQL,
|
||||||
self::COLLECTION,
|
self::COLLECTION,
|
||||||
$login
|
$login
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($instance->document instanceof _document) return $instance;
|
return $instance;
|
||||||
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
|
}
|
||||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
|
||||||
|
throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -225,20 +86,75 @@ final class account extends core
|
||||||
/**
|
/**
|
||||||
* Создать
|
* Создать
|
||||||
*
|
*
|
||||||
* @param array $data Данные аккаунта
|
* @param array &$errors Журнал ошибок
|
||||||
* @param array &$errors Реестр ошибок
|
|
||||||
*
|
*
|
||||||
* @return bool Создан аккаунт?
|
* @return ?_document Инстанция аккаунта, если удалось создать
|
||||||
*/
|
*/
|
||||||
public static function create(array $data = [], array &$errors = []): bool
|
public static function create(array &$errors = []): ?_document
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (collection::init(static::$db->session, self::COLLECTION))
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
if (document::write(static::$db->session, self::COLLECTION, $data)) return true;
|
// Инициализирована коллекция
|
||||||
else throw new exception('Не удалось создать аккаунт');
|
|
||||||
else throw new exception('Не удалось инициализировать коллекцию');
|
// Запись аккаунта в базу данных
|
||||||
|
$_id = document::write(static::$db->session, self::COLLECTION);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d._id == '$_id'
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден созданный аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else throw new exception('Не удалось создать аккаунт');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Связь аккаунта с аккаунтом ВКонтакте
|
||||||
|
*
|
||||||
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return bool Статус выполнения
|
||||||
|
*/
|
||||||
|
public static function connect(_document $account, _document $vk, array &$errors = []): bool
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, vk::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализированы коллекции
|
||||||
|
|
||||||
|
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, [
|
||||||
|
'_from' => $account->getId(),
|
||||||
|
'_to' => $vk->getId()
|
||||||
|
])) {
|
||||||
|
// Создано ребро: account -> vk
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else throw new exception('Не удалось создать ребро: account -> vk');
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -251,72 +167,56 @@ final class account extends core
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записать
|
* Поиск связанного аккаунта ВКонтакте
|
||||||
*
|
*
|
||||||
* Записывает свойство в инстанцию документа аккаунта из базы данных
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @param string $name Название
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
* @param mixed $value Содержимое
|
|
||||||
*
|
|
||||||
* @return void
|
|
||||||
*/
|
*/
|
||||||
public function __set(string $name, mixed $value = null): void
|
public static function vk(_document $account, array &$errors = []): ?_document
|
||||||
{
|
{
|
||||||
$this->document->{$name} = $value;
|
try {
|
||||||
}
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, vk::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
/**
|
if ($vk = collection::search(static::$db->session, sprintf(
|
||||||
* Прочитать
|
<<<AQL
|
||||||
*
|
FOR document IN %s
|
||||||
* Читает свойство из инстанции документа аккаунта из базы данных
|
LET edge = (
|
||||||
*
|
FOR edge IN %s
|
||||||
* @param string $name Название
|
FILTER edge._from == '%s'
|
||||||
*
|
SORT edge._key DESC
|
||||||
* @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных
|
LIMIT 1
|
||||||
*/
|
RETURN edge
|
||||||
public function __get(string $name): mixed
|
)
|
||||||
{
|
FILTER document._id == edge[0]._to
|
||||||
return $this->document->{$name};
|
LIMIT 1
|
||||||
}
|
RETURN document
|
||||||
|
AQL,
|
||||||
|
vk::COLLECTION,
|
||||||
|
self::COLLECTION . '_edge_' . vk::COLLECTION,
|
||||||
|
$account->getId()
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
/**
|
return $vk;
|
||||||
* Проверить инициализированность
|
} else throw new exception('Не удалось найти аккаунт ВКонтакте');
|
||||||
*
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
* Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных
|
} catch (exception $e) {
|
||||||
*
|
// Запись в журнал ошибок
|
||||||
* @param string $name Название
|
$errors[] = [
|
||||||
*
|
'text' => $e->getMessage(),
|
||||||
* @return bool Свойство инициализировано?
|
'file' => $e->getFile(),
|
||||||
*/
|
'line' => $e->getLine(),
|
||||||
public function __isset(string $name): bool
|
'stack' => $e->getTrace()
|
||||||
{
|
];
|
||||||
return isset($this->document->{$name});
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
return null;
|
||||||
* Удалить
|
|
||||||
*
|
|
||||||
* Деинициализировать свойство в инстанции документа аккаунта из базы данных
|
|
||||||
*
|
|
||||||
* @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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,30 +33,18 @@ class core extends model
|
||||||
*/
|
*/
|
||||||
protected static connection $db;
|
protected static connection $db;
|
||||||
|
|
||||||
/**
|
public function __construct(connection $db = null)
|
||||||
* Конструктор
|
|
||||||
*
|
|
||||||
* @param bool $initialize Инициализировать контроллер?
|
|
||||||
* @param connection $db Инстанция соединения с базой данных
|
|
||||||
*/
|
|
||||||
public function __construct(bool $initialize = true, connection $db = null)
|
|
||||||
{
|
{
|
||||||
parent::__construct($initialize);
|
if (isset($db)) {
|
||||||
|
// Получена инстанция соединения с базой данных
|
||||||
|
|
||||||
if ($initialize) {
|
// Запись и инициализация соединения с базой данных
|
||||||
// Запрошена инициализация
|
$this->__set('db', $db);
|
||||||
|
} else {
|
||||||
|
// Не получена инстанция соединения с базой данных
|
||||||
|
|
||||||
if (isset($db)) {
|
// Инициализация соединения с базой данных по умолчанию
|
||||||
// Получена инстанция соединения с базой данных
|
$this->__get('db');
|
||||||
|
|
||||||
// Запись и инициализация соединения с базой данных
|
|
||||||
$this->__set('db', $db);
|
|
||||||
} else {
|
|
||||||
// Не получена инстанция соединения с базой данных
|
|
||||||
|
|
||||||
// Инициализация соединения с базой данных по умолчанию
|
|
||||||
$this->__get('db');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ final class password extends core
|
||||||
* Сгенерировать мнемонический пароль
|
* Сгенерировать мнемонический пароль
|
||||||
*
|
*
|
||||||
* @param int $length Длина (количество слов)
|
* @param int $length Длина (количество слов)
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return ?string Пароль
|
* @return ?string Пароль
|
||||||
*/
|
*/
|
||||||
|
@ -107,7 +107,7 @@ final class password extends core
|
||||||
|
|
||||||
return implode(' ', $password);
|
return implode(' ', $password);
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -123,7 +123,7 @@ final class password extends core
|
||||||
* Сгенерировать классический пароль
|
* Сгенерировать классический пароль
|
||||||
*
|
*
|
||||||
* @param int $length Длина (количество символов)
|
* @param int $length Длина (количество символов)
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return ?string Пароль
|
* @return ?string Пароль
|
||||||
*/
|
*/
|
||||||
|
@ -145,7 +145,7 @@ final class password extends core
|
||||||
|
|
||||||
return $password;
|
return $password;
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
|
|
@ -31,15 +31,15 @@ final class invite extends core
|
||||||
public const COLLECTION = 'invite';
|
public const COLLECTION = 'invite';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инстанция документа приглашения в базе данных
|
* Инстанция в базе данных
|
||||||
*/
|
*/
|
||||||
public ?_document $document;
|
public ?_document $instance;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Прочитать
|
* Прочитать
|
||||||
*
|
*
|
||||||
* @param string $invite Ключ приглашения
|
* @param string $invite Ключ приглашения
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return ?self Инстанция приглашения, если оно найдено
|
* @return ?self Инстанция приглашения, если оно найдено
|
||||||
*/
|
*/
|
||||||
|
@ -53,24 +53,25 @@ final class invite extends core
|
||||||
$instance = new self;
|
$instance = new self;
|
||||||
|
|
||||||
// Поиск приглашения
|
// Поиск приглашения
|
||||||
$instance->document = collection::search(
|
$instance->instance = collection::search(
|
||||||
static::$db->session,
|
static::$db->session,
|
||||||
sprintf(
|
sprintf(
|
||||||
<<<AQL
|
<<<AQL
|
||||||
FOR d IN %s
|
FOR d IN %s
|
||||||
FILTER d.key == '%s' && d.active == true
|
FILTER d.key == '%s' && d.active == true
|
||||||
RETURN d
|
RETURN d
|
||||||
AQL,
|
AQL,
|
||||||
self::COLLECTION,
|
self::COLLECTION,
|
||||||
$invite
|
$invite
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
if ($instance->document instanceof _document) return $instance;
|
return $instance;
|
||||||
else throw new exception('Не удалось найти инстанцию приглашения в базе данных');
|
}
|
||||||
} throw new exception('Не удалось инициализировать коллекцию');
|
|
||||||
|
throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -84,6 +85,6 @@ final class invite extends core
|
||||||
|
|
||||||
public function from(): ?account
|
public function from(): ?account
|
||||||
{
|
{
|
||||||
return null;
|
return new account();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ final class session extends core
|
||||||
public const COLLECTION = 'session';
|
public const COLLECTION = 'session';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инстанция документа сессии в базе данных
|
* Данные сессии из базы данных
|
||||||
*/
|
*/
|
||||||
public _document $document;
|
public _document $document;
|
||||||
|
|
||||||
|
@ -42,7 +42,7 @@ final class session extends core
|
||||||
*
|
*
|
||||||
* @param ?string $hash Хеш сессии в базе данных
|
* @param ?string $hash Хеш сессии в базе данных
|
||||||
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
|
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return static Инстанция сессии
|
* @return static Инстанция сессии
|
||||||
*/
|
*/
|
||||||
|
@ -55,8 +55,8 @@ final class session extends core
|
||||||
if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
|
if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
|
||||||
<<<AQL
|
<<<AQL
|
||||||
FOR d IN %s
|
FOR d IN %s
|
||||||
FILTER d.hash == '$hash' && d.expires > %d && d.status == 'active'
|
FILTER d.hash == '$hash' && d.expires > %d
|
||||||
RETURN d
|
RETURN d
|
||||||
AQL,
|
AQL,
|
||||||
self::COLLECTION,
|
self::COLLECTION,
|
||||||
time()
|
time()
|
||||||
|
@ -68,8 +68,8 @@ final class session extends core
|
||||||
} else if ($session = collection::search(static::$db->session, sprintf(
|
} else if ($session = collection::search(static::$db->session, sprintf(
|
||||||
<<<AQL
|
<<<AQL
|
||||||
FOR d IN %s
|
FOR d IN %s
|
||||||
FILTER d.ip == '%s' && d.expires > %d && d.status == 'active'
|
FILTER d.ip == '%s' && d.expires > %d
|
||||||
RETURN d
|
RETURN d
|
||||||
AQL,
|
AQL,
|
||||||
self::COLLECTION,
|
self::COLLECTION,
|
||||||
$_SERVER['REMOTE_ADDR'],
|
$_SERVER['REMOTE_ADDR'],
|
||||||
|
@ -84,21 +84,20 @@ final class session extends core
|
||||||
|
|
||||||
// Запись сессии в базу данных
|
// Запись сессии в базу данных
|
||||||
$_id = document::write(static::$db->session, self::COLLECTION, [
|
$_id = document::write(static::$db->session, self::COLLECTION, [
|
||||||
'status' => 'active',
|
'ip' => $_SERVER['REMOTE_ADDR'],
|
||||||
'expires' => $expires ?? time() + 604800,
|
'expires' => $expires ?? time() + 604800
|
||||||
'ip' => $_SERVER['REMOTE_ADDR']
|
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if ($session = collection::search(static::$db->session, sprintf(
|
if ($session = collection::search(static::$db->session, sprintf(
|
||||||
<<<AQL
|
<<<AQL
|
||||||
FOR d IN %s
|
FOR d IN %s
|
||||||
FILTER d._id == '$_id' && d.expires > %d && d.status == 'active'
|
FILTER d._id == '$_id' && d.expires > %d
|
||||||
RETURN d
|
RETURN d
|
||||||
AQL,
|
AQL,
|
||||||
self::COLLECTION,
|
self::COLLECTION,
|
||||||
time()
|
time()
|
||||||
))) {
|
))) {
|
||||||
// Найдена только что созданная сессия
|
// Найдена созданная сессия
|
||||||
|
|
||||||
// Запись хеша
|
// Запись хеша
|
||||||
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
|
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
|
||||||
|
@ -113,7 +112,7 @@ final class session extends core
|
||||||
}
|
}
|
||||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -129,16 +128,14 @@ final class session extends core
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Инициализировать связб сессии с аккаунтом
|
* Связь сессии с аккаунтом
|
||||||
*
|
*
|
||||||
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
|
* @param _document $account Инстанция аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @param account $account Инстанция аккаунта
|
* @return bool Статус выполнения
|
||||||
* @param array &$errors Реестр ошибок
|
|
||||||
*
|
|
||||||
* @return bool Связан аккаунт?
|
|
||||||
*/
|
*/
|
||||||
public function connect(account $account, array &$errors = []): bool
|
public function connect(_document $account, array &$errors = []): bool
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
@ -148,30 +145,17 @@ final class session extends core
|
||||||
) {
|
) {
|
||||||
// Инициализирована коллекция
|
// Инициализирована коллекция
|
||||||
|
|
||||||
if (
|
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
|
||||||
collection::search(static::$db->session, sprintf(
|
'_from' => $this->document->getId(),
|
||||||
<<<AQL
|
'_to' => $account->getId()
|
||||||
FOR document IN %s
|
])) {
|
||||||
FILTER document._from == '%s' && document._to == '%s'
|
// Создано ребро: session -> account
|
||||||
LIMIT 1
|
|
||||||
RETURN document
|
|
||||||
AQL,
|
|
||||||
self::COLLECTION . '_edge_' . account::COLLECTION,
|
|
||||||
$this->document->getId(),
|
|
||||||
$account->getId()
|
|
||||||
)) instanceof _document
|
|
||||||
|| document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
|
|
||||||
'_from' => $this->document->getId(),
|
|
||||||
'_to' => $account->getId()
|
|
||||||
])
|
|
||||||
) {
|
|
||||||
// Найдено, либо создано ребро: session -> account
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} else throw new exception('Не удалось создать ребро: session -> account');
|
} else throw new exception('Не удалось создать ребро: session -> account');
|
||||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -184,13 +168,13 @@ final class session extends core
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Найти связанный аккаунт
|
* Поиск связанного аккаунта
|
||||||
*
|
*
|
||||||
* @param array &$errors Реестр ошибок
|
* @param array &$errors Журнал ошибок
|
||||||
*
|
*
|
||||||
* @return ?account Инстанция аккаунта, если удалось найти
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
*/
|
*/
|
||||||
public function account(array &$errors = []): ?account
|
public function account(array &$errors = []): ?_document
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
if (
|
if (
|
||||||
|
@ -200,34 +184,31 @@ final class session extends core
|
||||||
) {
|
) {
|
||||||
// Инициализированы коллекции
|
// Инициализированы коллекции
|
||||||
|
|
||||||
// Инициализация инстанции аккаунта
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
$account = new account;
|
|
||||||
|
|
||||||
// Поиск инстанции аккаунта в базе данных
|
|
||||||
$account->document = collection::search(static::$db->session, sprintf(
|
|
||||||
<<<AQL
|
<<<AQL
|
||||||
FOR document IN %s
|
FOR document IN %s
|
||||||
LET edge = (
|
LET edge = (
|
||||||
FOR edge IN %s
|
FOR edge IN %s
|
||||||
FILTER edge._from == '%s'
|
FILTER edge._from == '%s'
|
||||||
SORT edge._key DESC
|
SORT edge._key DESC
|
||||||
LIMIT 1
|
|
||||||
RETURN edge
|
|
||||||
)
|
|
||||||
FILTER document._id == edge[0]._to
|
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
RETURN document
|
RETURN edge
|
||||||
|
)
|
||||||
|
FILTER document._id == edge[0]._to
|
||||||
|
LIMIT 1
|
||||||
|
RETURN document
|
||||||
AQL,
|
AQL,
|
||||||
account::COLLECTION,
|
account::COLLECTION,
|
||||||
self::COLLECTION . '_edge_' . account::COLLECTION,
|
self::COLLECTION . '_edge_' . account::COLLECTION,
|
||||||
$this->getId()
|
$this->document->getId()
|
||||||
));
|
))) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
if ($account->document instanceof _document) return $account;
|
return $account;
|
||||||
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
|
} else throw new exception('Не удалось найти аккаунт');
|
||||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
} catch (exception $e) {
|
} catch (exception $e) {
|
||||||
// Запись в реестр ошибок
|
// Запись в журнал ошибок
|
||||||
$errors[] = [
|
$errors[] = [
|
||||||
'text' => $e->getMessage(),
|
'text' => $e->getMessage(),
|
||||||
'file' => $e->getFile(),
|
'file' => $e->getFile(),
|
||||||
|
@ -239,47 +220,6 @@ final class session extends core
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Записать в буфер сессии
|
|
||||||
*
|
|
||||||
* @param array $data Данные для записи
|
|
||||||
* @param array &$errors Реестр ошибок
|
|
||||||
*
|
|
||||||
* @return bool Записаны данные в буфер сессии?
|
|
||||||
*/
|
|
||||||
public function write(array $data, array &$errors = []): bool
|
|
||||||
{
|
|
||||||
try {
|
|
||||||
if (collection::init(static::$db->session, self::COLLECTION)) {
|
|
||||||
// Инициализирована коллекция
|
|
||||||
|
|
||||||
// Проверка инициализированности инстанции документа из базы данных
|
|
||||||
if (!isset($this->document)) throw new exception('Не инициализирована инстанция документа из базы данных');
|
|
||||||
|
|
||||||
// Запись параметров в инстанцию документа из базы данных
|
|
||||||
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
|
|
||||||
|
|
||||||
if (document::update(static::$db->session, $this->document)) {
|
|
||||||
// Записано обновление
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new exception('Не удалось записать данные в буфер сессии');
|
|
||||||
} else throw new exception('Не удалось инициализировать коллекцию');
|
|
||||||
} catch (exception $e) {
|
|
||||||
// Запись в реестр ошибок
|
|
||||||
$errors[] = [
|
|
||||||
'text' => $e->getMessage(),
|
|
||||||
'file' => $e->getFile(),
|
|
||||||
'line' => $e->getLine(),
|
|
||||||
'stack' => $e->getTrace()
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Записать
|
* Записать
|
||||||
*
|
*
|
||||||
|
@ -347,6 +287,6 @@ final class session extends core
|
||||||
*/
|
*/
|
||||||
public function __call(string $name, array $arguments = [])
|
public function __call(string $name, array $arguments = [])
|
||||||
{
|
{
|
||||||
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
|
if (method_exists($this, $name)) return $this->document->{$name}($arguments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,555 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\site\account\models;
|
||||||
|
|
||||||
|
// Файлы проекта
|
||||||
|
use mirzaev\site\account\models\account;
|
||||||
|
|
||||||
|
// Фреймворк ArangoDB
|
||||||
|
use mirzaev\arangodb\collection,
|
||||||
|
mirzaev\arangodb\document;
|
||||||
|
|
||||||
|
// Фреймворк ВКонтакте
|
||||||
|
use mirzaev\vk\robots\user as robot;
|
||||||
|
|
||||||
|
// Библиотека для ArangoDB
|
||||||
|
use ArangoDBClient\Document as _document;
|
||||||
|
|
||||||
|
// Библиотека браузера
|
||||||
|
use GuzzleHttp\Client as browser;
|
||||||
|
|
||||||
|
// Встроенные библиотеки
|
||||||
|
use exception;
|
||||||
|
use stdClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель аккаунта ВКонтакте
|
||||||
|
*
|
||||||
|
* @package mirzaev\site\account\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||||||
|
*/
|
||||||
|
final class vk extends core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Коллекция
|
||||||
|
*/
|
||||||
|
public const COLLECTION = 'vk';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация
|
||||||
|
*
|
||||||
|
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
|
||||||
|
*/
|
||||||
|
public static function initialization(string $response = '', array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
// Инициализация данных аккаунта ВКонтакте
|
||||||
|
$data = json_decode($response);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d.id == $data->user_id
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} else {
|
||||||
|
// Не найден аккаунт ВКонтакте
|
||||||
|
|
||||||
|
return self::create($response, $errors);
|
||||||
|
}
|
||||||
|
} else throw new exception('Не удалось инициализировать коллекцию');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создание
|
||||||
|
*
|
||||||
|
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
|
||||||
|
*/
|
||||||
|
public static function create(string $response = '', array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
// Запись аккаунта в базу данных
|
||||||
|
$_id = document::write(static::$db->session, self::COLLECTION);
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR d IN %s
|
||||||
|
FILTER d._id == '$_id'
|
||||||
|
RETURN d
|
||||||
|
AQL,
|
||||||
|
self::COLLECTION
|
||||||
|
))) {
|
||||||
|
// Найден созданный аккаунт ВКонтакте
|
||||||
|
|
||||||
|
if (document::update(static::$db->session, $account)) {
|
||||||
|
// Записано обновление
|
||||||
|
|
||||||
|
// Запись данных об аккаунте ВКонтакте и возврат (bool)
|
||||||
|
return self::update($account, json_decode($response), $errors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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 $code Код полученный от ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?string Тело ответа, если получен код ответа 200
|
||||||
|
*/
|
||||||
|
public static function key(string $code = '', array &$errors = []): ?string
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Инициализация браузера
|
||||||
|
$browser = new browser();
|
||||||
|
|
||||||
|
// Запрос
|
||||||
|
$response = $browser->request('GET', "https://oauth.vk.com/access_token?client_id=51447080&client_secret=KYlk0nGELW0A9ds7NQi6&redirect_uri=https://mirzaev.sexy/account/vk/connect&code=$code");
|
||||||
|
|
||||||
|
if ($response->getStatusCode() === 200) {
|
||||||
|
// Ответ сервера: 200
|
||||||
|
|
||||||
|
return (string) $response->getBody();
|
||||||
|
} else throw new exception('Не удалось получить ключ ВКонтакте (' . $response->getStatusCode() . ')');
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск связанного аккаунта
|
||||||
|
*
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта, если удалось найти
|
||||||
|
*/
|
||||||
|
public static function account(_document $vk, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (
|
||||||
|
collection::init(static::$db->session, self::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION)
|
||||||
|
&& collection::init(static::$db->session, account::COLLECTION . '_edge_' . self::COLLECTION, true)
|
||||||
|
) {
|
||||||
|
// Инициализированы коллекции
|
||||||
|
|
||||||
|
if ($account = collection::search(static::$db->session, sprintf(
|
||||||
|
<<<AQL
|
||||||
|
FOR document IN %s
|
||||||
|
LET edge = (
|
||||||
|
FOR edge IN %s
|
||||||
|
FILTER edge._to == '%s'
|
||||||
|
SORT edge._key DESC
|
||||||
|
LIMIT 1
|
||||||
|
RETURN edge
|
||||||
|
)
|
||||||
|
FILTER document._id == edge[0]._from
|
||||||
|
LIMIT 1
|
||||||
|
RETURN document
|
||||||
|
AQL,
|
||||||
|
account::COLLECTION,
|
||||||
|
account::COLLECTION . '_edge_' . self::COLLECTION,
|
||||||
|
$vk->getId()
|
||||||
|
))) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
return $account;
|
||||||
|
} 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 robot $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?stdClass Данные аккаунта ВКонтакте, если получены
|
||||||
|
*/
|
||||||
|
public static function parse(robot $vk, array &$errors = []): ?stdClass
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// Запрос к API-серверу ВКонтакте
|
||||||
|
$response = $vk->user->get(fields: [
|
||||||
|
'activities',
|
||||||
|
'about',
|
||||||
|
// 'blacklisted',
|
||||||
|
// 'blacklisted_by_me',
|
||||||
|
'books',
|
||||||
|
'bdate',
|
||||||
|
'can_be_invited_group',
|
||||||
|
'can_post',
|
||||||
|
'can_see_all_posts',
|
||||||
|
'can_see_audio',
|
||||||
|
'can_send_friend_request',
|
||||||
|
'can_write_private_message',
|
||||||
|
'career',
|
||||||
|
'common_count',
|
||||||
|
'connections',
|
||||||
|
'contacts',
|
||||||
|
'city',
|
||||||
|
'country',
|
||||||
|
'crop_photo',
|
||||||
|
'domain',
|
||||||
|
'education',
|
||||||
|
'exports',
|
||||||
|
'followers_count',
|
||||||
|
'friend_status',
|
||||||
|
'has_photo',
|
||||||
|
'has_mobile',
|
||||||
|
'home_town',
|
||||||
|
'photo_50',
|
||||||
|
'photo_100',
|
||||||
|
'photo_200',
|
||||||
|
'photo_200_orig',
|
||||||
|
'photo_400_orig',
|
||||||
|
'photo_max',
|
||||||
|
'photo_max_orig',
|
||||||
|
'sex',
|
||||||
|
'site',
|
||||||
|
'schools',
|
||||||
|
'screen_name',
|
||||||
|
'status',
|
||||||
|
'verified',
|
||||||
|
'games',
|
||||||
|
'interests',
|
||||||
|
'is_favorite',
|
||||||
|
'is_friend',
|
||||||
|
'is_hidden_from_feed',
|
||||||
|
'last_seen',
|
||||||
|
'maiden_name',
|
||||||
|
'military',
|
||||||
|
'movies',
|
||||||
|
'music',
|
||||||
|
'nickname',
|
||||||
|
'occupation',
|
||||||
|
'online',
|
||||||
|
'personal',
|
||||||
|
'photo_id',
|
||||||
|
'quotes',
|
||||||
|
'relation',
|
||||||
|
'relatives',
|
||||||
|
'timezone',
|
||||||
|
'tv',
|
||||||
|
'universities'
|
||||||
|
])[0];
|
||||||
|
|
||||||
|
if (!empty($response)) {
|
||||||
|
// Получен ответ
|
||||||
|
|
||||||
|
return $response;
|
||||||
|
}
|
||||||
|
} catch (exception $e) {
|
||||||
|
// Запись в журнал ошибок
|
||||||
|
$errors[] = [
|
||||||
|
'text' => $e->getMessage(),
|
||||||
|
'file' => $e->getFile(),
|
||||||
|
'line' => $e->getLine(),
|
||||||
|
'stack' => $e->getTrace()
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление данных аккаунта ВКонтакте
|
||||||
|
*
|
||||||
|
* Все файлы (аватар, например) будут скачаны на сервер
|
||||||
|
*
|
||||||
|
* @param _document $vk Инстанция аккаунта ВКонтакте
|
||||||
|
* @param stdClass $data Информация об аккаунте (self::parse() или json_decode())
|
||||||
|
* @param array &$errors Журнал ошибок
|
||||||
|
*
|
||||||
|
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось обновить
|
||||||
|
*/
|
||||||
|
public static function update(_document $vk, stdClass $data, array &$errors = []): ?_document
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
if (collection::init(static::$db->session, self::COLLECTION)) {
|
||||||
|
// Инициализирована коллекция
|
||||||
|
|
||||||
|
if (empty($vk->id) and isset($data->user_id) || isset($data->id)) {
|
||||||
|
// Получен идентификатор
|
||||||
|
|
||||||
|
// Запись
|
||||||
|
$vk->id = $data->user_id ?? $data->id;
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->user_id, $data->id);
|
||||||
|
} else if (empty($vk->id)) throw new exception('Не удалось найти идентификатор аккаунта ВКонтакте');
|
||||||
|
|
||||||
|
if (isset($data->access_token, $data->expires_in)) {
|
||||||
|
// Получен ключ
|
||||||
|
|
||||||
|
// Запись
|
||||||
|
$vk->access = [
|
||||||
|
'key' => $data->access_token,
|
||||||
|
'expires' => $data->expires_in
|
||||||
|
];
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->access_token, $data->expires_in);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация браузера
|
||||||
|
$browser = new browser();
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_50)) {
|
||||||
|
// Получено изображение 50x50
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_50, ['sink' => $file = "$path/50x50.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'50x50' => ($vk->cover['50x50'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_50,
|
||||||
|
'public' => "/storage/$vk->id/cover/50x50.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 50x50 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_50);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_100)) {
|
||||||
|
// Получено изображение 100x100
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_100, ['sink' => $file = "$path/100x100.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'100x100' => ($vk->cover['100x100'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_100,
|
||||||
|
'public' => "/storage/$vk->id/cover/100x100.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 100x100 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_100);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Инициализация директории с обложкой
|
||||||
|
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
|
||||||
|
mkdir($path, 0775, true);
|
||||||
|
|
||||||
|
if (isset($data->photo_200)) {
|
||||||
|
// Получено изображение 200x200
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_200, ['sink' => $file = "$path/200x200.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'200x200' => ($vk->cover['200x200'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_200,
|
||||||
|
'public' => "/storage/$vk->id/cover/200x200.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 200x200 с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_200);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_200_orig)) {
|
||||||
|
// Получено изображение 200x
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_200_orig, ['sink' => $file = "$path/200x.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'200x' => ($vk->cover['200x'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_200_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/200x.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 200x с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_200_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_400_orig)) {
|
||||||
|
// Получено изображение 400x
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_400_orig, ['sink' => $file = "$path/400x.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'400x' => ($vk->cover['400x'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_400_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/400x.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение 400x с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_400_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_max)) {
|
||||||
|
// Получено изображение MAXxMAX
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max, ['sink' => $file = "$path/MAXxMAX.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXxMAX' => ($vk->cover['MAXxMAX'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXxMAX.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXxMAX с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->photo_max_orig)) {
|
||||||
|
// Получено изображение MAXx
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXx' => ($vk->cover['MAXx'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXx.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isset($data->crop_photo)) {
|
||||||
|
// Получено изображение MAXx
|
||||||
|
|
||||||
|
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
|
||||||
|
$vk->cover =
|
||||||
|
($vk->cover ?? []) +
|
||||||
|
[
|
||||||
|
'MAXx' => ($vk->cover['MAXx'] ?? []) +
|
||||||
|
[
|
||||||
|
'source' => $data->photo_max_orig,
|
||||||
|
'public' => "/storage/$vk->id/cover/MAXx.jpg",
|
||||||
|
'local' => $file,
|
||||||
|
]
|
||||||
|
];
|
||||||
|
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
|
||||||
|
|
||||||
|
// Удаление из списка необработанных
|
||||||
|
unset($data->photo_max_orig);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Перебор оставшихся параметров
|
||||||
|
foreach ($data as $key => $value) $vk->{$key} = $value;
|
||||||
|
|
||||||
|
if (document::update(static::$db->session, $vk)) {
|
||||||
|
// Записано обновление
|
||||||
|
|
||||||
|
return $vk;
|
||||||
|
} 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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -19,14 +19,7 @@ main {
|
||||||
align-items: unset;
|
align-items: unset;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.column {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel {
|
section.panel {
|
||||||
--display : flex;
|
|
||||||
z-index : 1000;
|
z-index : 1000;
|
||||||
width : 400px;
|
width : 400px;
|
||||||
position : absolute;
|
position : absolute;
|
||||||
|
@ -34,10 +27,6 @@ section.panel {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
|
|
||||||
div.column>section.panel {
|
|
||||||
position : unset;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel.medium {
|
section.panel.medium {
|
||||||
width: 300px;
|
width: 300px;
|
||||||
}
|
}
|
||||||
|
@ -54,6 +43,7 @@ section.panel#classic {
|
||||||
margin-left: 570px;
|
margin-left: 570px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
section.panel>section.body>ul {
|
section.panel>section.body>ul {
|
||||||
margin: 0 5%;
|
margin: 0 5%;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
@ -71,34 +61,6 @@ section.panel>section.body>ul>li {
|
||||||
animation-fill-mode : forwards;
|
animation-fill-mode : forwards;
|
||||||
animation-timing-function: cubic-bezier(.47,0,.74,.71);
|
animation-timing-function: cubic-bezier(.47,0,.74,.71);
|
||||||
}
|
}
|
||||||
|
|
||||||
section.panel>section.body>dl {
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel>section.body>dl>* {
|
|
||||||
word-break: break-word;
|
|
||||||
animation-duration : .35s;
|
|
||||||
animation-name : uprise;
|
|
||||||
animation-fill-mode : forwards;
|
|
||||||
animation-timing-function: cubic-bezier(.47,0,.74,.71);
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel>section.body>dl>dt {
|
|
||||||
margin-left: 20px;
|
|
||||||
display: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel>section.body>dl>dd {
|
|
||||||
margin-left: unset;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
section.panel>section.header {
|
section.panel>section.header {
|
||||||
z-index : 1000;
|
z-index : 1000;
|
||||||
height : 50px;
|
height : 50px;
|
||||||
|
|
|
@ -21,44 +21,3 @@
|
||||||
filter: blur(0px);
|
filter: blur(0px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes window-vertical-open {
|
|
||||||
0% {
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
height: var(--height, inherit);
|
|
||||||
opacity: var(--opacity, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes window-vertical-close {
|
|
||||||
0% {
|
|
||||||
height: var(--height, inherit);
|
|
||||||
opacity: var(--opacity, 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
100% {
|
|
||||||
height: 0;
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.animation.window:not(.hidden, .horizontal) {
|
|
||||||
overflow: hidden;
|
|
||||||
animation-duration : .1s;
|
|
||||||
animation-name : window-vertical-open;
|
|
||||||
animation-fill-mode : forwards;
|
|
||||||
animation-timing-function: ease-in;
|
|
||||||
}
|
|
||||||
|
|
||||||
.animation.window.hidden:not(.horizontal) {
|
|
||||||
overflow: hidden;
|
|
||||||
animation-duration : .05s;
|
|
||||||
animation-name : window-vertical-close;
|
|
||||||
animation-fill-mode : forwards;
|
|
||||||
animation-timing-function: ease-out;
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
@import url('/fonts/commissioner.ttf');
|
||||||
|
|
||||||
@media (prefers-color-scheme: light) {
|
@media (prefers-color-scheme: light) {
|
||||||
:root {
|
:root {
|
||||||
--background-above-1 : #fff;
|
--background-above-1 : #fff;
|
||||||
|
@ -79,8 +81,9 @@
|
||||||
user-select : none;
|
user-select : none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.hidden:not(.animation) {
|
.hidden {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
opacity: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
* {
|
* {
|
||||||
|
|
Binary file not shown.
|
@ -39,7 +39,11 @@ $router->write('/session/password', 'session', 'password', 'POST');
|
||||||
$router->write('/session/invite', 'session', 'invite', 'POST');
|
$router->write('/session/invite', 'session', 'invite', 'POST');
|
||||||
|
|
||||||
// Инициализация ядра
|
// Инициализация ядра
|
||||||
$core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false));
|
$core = new core(namespace: __NAMESPACE__, router: $router);
|
||||||
|
|
||||||
|
// Инициализация ядер
|
||||||
|
$core->controller = new controller;
|
||||||
|
$core->model = new model;
|
||||||
|
|
||||||
// Обработка запроса
|
// Обработка запроса
|
||||||
echo $core->start();
|
echo $core->start();
|
||||||
|
|
|
@ -9,7 +9,6 @@ class password {
|
||||||
* @return {object} {(string) password, (array) errors}
|
* @return {object} {(string) password, (array) errors}
|
||||||
*/
|
*/
|
||||||
static async generate(length = 12, type = "classic") {
|
static async generate(length = 12, type = "classic") {
|
||||||
// Запрос к серверу
|
|
||||||
return await fetch("https://account.mirzaev.sexy/api/generate/password", {
|
return await fetch("https://account.mirzaev.sexy/api/generate/password", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
|
|
@ -6,12 +6,12 @@ class session {
|
||||||
*
|
*
|
||||||
* Записывает входной псевдоним в сессию, а так же проверяет существование аккаунта с ним
|
* Записывает входной псевдоним в сессию, а так же проверяет существование аккаунта с ним
|
||||||
*
|
*
|
||||||
* @param {string} login Входной псевдоним
|
* @param {string} login Входной
|
||||||
*
|
*
|
||||||
* @return {object} {(bool) exist, (array) errors}
|
* @return {object} {(bool) exist, (array) errors}
|
||||||
*/
|
*/
|
||||||
static async login(login) {
|
static async login(login) {
|
||||||
// Запрос к серверу
|
// Запрос
|
||||||
return await fetch('https://account.mirzaev.sexy/session/login', {
|
return await fetch('https://account.mirzaev.sexy/session/login', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -32,16 +32,16 @@ class session {
|
||||||
*
|
*
|
||||||
* @param {string} password Пароль
|
* @param {string} password Пароль
|
||||||
*
|
*
|
||||||
* @return {object} {(bool) verify, (bool) account, (array) errors}
|
* @return {object} {(bool) verify, (array) errors}
|
||||||
*/
|
*/
|
||||||
static async password(password) {
|
static async password(password) {
|
||||||
// Запрос к серверу
|
// Запрос
|
||||||
return await fetch('https://account.mirzaev.sexy/session/password', {
|
return await fetch('https://account.mirzaev.sexy/session/password', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
'Content-Type': 'application/x-www-form-urlencoded',
|
||||||
},
|
},
|
||||||
body: `password=${password}&remember=1&return=verify,account,errors`
|
body: `password=${password}&return=verify,errors`
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
|
@ -59,7 +59,7 @@ class session {
|
||||||
* @return {object} {(bool) exist, (array) from, (array) errors}
|
* @return {object} {(bool) exist, (array) from, (array) errors}
|
||||||
*/
|
*/
|
||||||
static async invite(invite) {
|
static async invite(invite) {
|
||||||
// Запрос к серверу
|
// Запрос
|
||||||
return await fetch("https://account.mirzaev.sexy/session/invite", {
|
return await fetch("https://account.mirzaev.sexy/session/invite", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers: {
|
||||||
|
@ -69,16 +69,6 @@ class session {
|
||||||
})
|
})
|
||||||
.then((response) => response.json())
|
.then((response) => response.json())
|
||||||
.then((data) => {
|
.then((data) => {
|
||||||
if (data.exist === false) {
|
|
||||||
// Не найдено приглашение
|
|
||||||
|
|
||||||
// Инициализация категории ошибок
|
|
||||||
if (typeof data.errors.session === 'undefined') data.errors.session = [];
|
|
||||||
|
|
||||||
// Запись ошибки
|
|
||||||
data.errors.session.push('Не найдено приглашение');
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,247 @@
|
||||||
|
{% block css %}
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/account.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icons/arrow_right.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icons/nametag.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icons/keyhole.css">
|
||||||
|
<link type="text/css" rel="stylesheet" href="/css/icons/user_add.css">
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<section id="entry" class="panel medium">
|
||||||
|
<section class="header unselectable">
|
||||||
|
<h1>Идентификация</h1>
|
||||||
|
</section>
|
||||||
|
<section class="body">
|
||||||
|
<label id="login">
|
||||||
|
<i class="nametag"></i>
|
||||||
|
<input name="login" type="text" placeholder="Входной псевдоним"
|
||||||
|
value="{{ account.login ?? session.buffer.login ?? cookie.buffer_login }}"
|
||||||
|
onkeypress="if (event.keyCode === 13) _login()" autofocus>
|
||||||
|
<button class="accept" onclick="_login()"><i class="arrow right"></i></button>
|
||||||
|
</label>
|
||||||
|
<label id="password" class="hidden">
|
||||||
|
<i class="keyhole"></i>
|
||||||
|
<input name="password" type="password" placeholder="Пароль" autocomplete="current-password"
|
||||||
|
onkeypress="if (event.keyCode === 13) _password()">
|
||||||
|
<button class="accept" onclick="_password()"><i class="arrow right"></i></button>
|
||||||
|
</label>
|
||||||
|
<label id="invite" class="hidden">
|
||||||
|
<i class="user add"></i>
|
||||||
|
<input name="invite" type="text" placeholder="Ключ приглашения" onkeypress="if (event.keyCode === 13) _invite()">
|
||||||
|
<button class="accept" onclick="_invite()"><i class="arrow right"></i></button>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section id="mnemonic" class="panel small hidden">
|
||||||
|
<section class="header unselectable">
|
||||||
|
<h2>Мнемонические</h2>
|
||||||
|
</section>
|
||||||
|
<section class="body">
|
||||||
|
<ul></ul>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section id="classic" class="panel small hidden">
|
||||||
|
<section class="header unselectable">
|
||||||
|
<h2>Классические</h2>
|
||||||
|
</section>
|
||||||
|
<section class="body">
|
||||||
|
<ul></ul>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<script>
|
||||||
|
// Инициализация реестра ошибок
|
||||||
|
let errors = new Map;
|
||||||
|
|
||||||
|
// Инициализация функций в глобальной области видимости
|
||||||
|
let _login, _password, _invite
|
||||||
|
document.addEventListener('damper.initialized', function (e) {
|
||||||
|
// Инициализирован демпфер
|
||||||
|
|
||||||
|
// Инициализация узлов
|
||||||
|
const entry = document.getElementById('entry');
|
||||||
|
const mnemonic = document.getElementById('mnemonic');
|
||||||
|
const classic = document.getElementById('classic');
|
||||||
|
|
||||||
|
// Инициализация элемента с заголовком
|
||||||
|
const title = entry.getElementsByTagName('h1')[0];
|
||||||
|
|
||||||
|
// Инициализация элементов-оболочек полей ввода
|
||||||
|
const labels = {
|
||||||
|
login: document.getElementById('login'),
|
||||||
|
invite: document.getElementById('invite'),
|
||||||
|
password: document.getElementById('password')
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить входной псевдоним на сервер
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
_login = () => {
|
||||||
|
// Инициализация поля ввода
|
||||||
|
const input = labels.login.querySelector('input[name=login]');
|
||||||
|
|
||||||
|
// Блокировка поля ввода
|
||||||
|
input.disabled = true;
|
||||||
|
|
||||||
|
return e.detail.damper(async () => {
|
||||||
|
// Запрос к серверу
|
||||||
|
const response = await session.login(input.value, errors);
|
||||||
|
|
||||||
|
if (response.exist) {
|
||||||
|
// Найден аккаунт
|
||||||
|
|
||||||
|
// Инициализация интерфейса аутентификации
|
||||||
|
title.innerText = 'Аутентификация';
|
||||||
|
labels.login.classList.add('hidden');
|
||||||
|
labels.password.classList.remove('hidden');
|
||||||
|
labels.password.querySelector('input[name=password]').focus();
|
||||||
|
} else {
|
||||||
|
// Не найден аккаунт
|
||||||
|
|
||||||
|
// Инициализация интерфейса регистрации
|
||||||
|
title.innerText = 'Регистрация';
|
||||||
|
labels.login.classList.add('hidden');
|
||||||
|
labels.invite.classList.remove('hidden');
|
||||||
|
labels.invite.querySelector('input[name=invite]').focus();
|
||||||
|
}
|
||||||
|
}, 1000)();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить пароль на сервер
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
_password = () => {
|
||||||
|
// Инициализация поля ввода
|
||||||
|
const input = labels.password.querySelector('input[name=password]');
|
||||||
|
|
||||||
|
// Блокировка поля ввода
|
||||||
|
input.disabled = true;
|
||||||
|
|
||||||
|
return e.detail.damper(async () => {
|
||||||
|
// Деинициализация индикатора и анимации об ошибке
|
||||||
|
input.classList.remove('error');
|
||||||
|
|
||||||
|
// Запрос к серверу
|
||||||
|
const response = await session.password(input.value, errors);
|
||||||
|
|
||||||
|
if (response.verify) {
|
||||||
|
// Пройдена проверка пароля на соответствие требованиям
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Не пройдена проверка пароля на соответствие требованиям
|
||||||
|
|
||||||
|
// Разблокировка поля для ввода
|
||||||
|
input.disabled = false;
|
||||||
|
|
||||||
|
// Инициализация отображения ошибки
|
||||||
|
input.classList.add('error');
|
||||||
|
|
||||||
|
// Фокусировка на поле ввода
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}, 1000)();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправить код приглашения на сервер
|
||||||
|
*
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
_invite = () => {
|
||||||
|
// Инициализация поля ввода
|
||||||
|
const input = labels.invite.querySelector('input[name=invite]');
|
||||||
|
|
||||||
|
// Блокировка поля ввода
|
||||||
|
input.disabled = true;
|
||||||
|
|
||||||
|
return e.detail.damper(async () => {
|
||||||
|
// Деинициализация индикатора и анимации об ошибке
|
||||||
|
input.classList.remove('error');
|
||||||
|
|
||||||
|
// Запрос к серверу
|
||||||
|
const response = await session.invite(input.value, errors);
|
||||||
|
|
||||||
|
if (response.exist) {
|
||||||
|
// Найдено приглашение
|
||||||
|
|
||||||
|
// Инициализация интерфейса ввода пароля
|
||||||
|
labels.invite.classList.add('hidden');
|
||||||
|
labels.password.querySelector('input[name=password]').autocomplete = 'new-password';
|
||||||
|
labels.password.classList.remove('hidden');
|
||||||
|
mnemonic.classList.remove('hidden');
|
||||||
|
classic.classList.remove('hidden');
|
||||||
|
|
||||||
|
for (let i = 0; i < 6; i++) {
|
||||||
|
// Генерация HTML-элементов с примерами мнемонических паролей
|
||||||
|
|
||||||
|
// Инициализация HTML-элемента
|
||||||
|
const element = document.createElement('li');
|
||||||
|
|
||||||
|
// Генерация пароля
|
||||||
|
password.generate(Math.floor(Math.random() * 3) + 2, 'mnemonic')
|
||||||
|
.then((responce) => {
|
||||||
|
if (responce.password === '') {
|
||||||
|
// Не удалось сгенерировать пароль
|
||||||
|
|
||||||
|
// Перезапуск итерации
|
||||||
|
--i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись пароля
|
||||||
|
element.innerText = responce.password;
|
||||||
|
|
||||||
|
// Запись HTML-элемента с паролем в список
|
||||||
|
mnemonic.getElementsByTagName('ul')[0].appendChild(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < 8; i++) {
|
||||||
|
// Генерация HTML-элементов с примерами классических паролей
|
||||||
|
|
||||||
|
// Инициализация HTML-элемента
|
||||||
|
const element = document.createElement('li');
|
||||||
|
|
||||||
|
// Генерация пароля
|
||||||
|
password.generate(Math.floor(Math.random() * 14) + 4)
|
||||||
|
.then((responce) => {
|
||||||
|
if (responce.password === '') {
|
||||||
|
// Не удалось сгенерировать пароль
|
||||||
|
|
||||||
|
// Перезапуск итерации
|
||||||
|
--i;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запись пароля
|
||||||
|
element.innerText = responce.password;
|
||||||
|
|
||||||
|
// Запись HTML-элемента с паролем в список
|
||||||
|
classic.getElementsByTagName('ul')[0].appendChild(element);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Не найдено приглашение
|
||||||
|
|
||||||
|
// Разблокировка поля для ввода
|
||||||
|
input.disabled = false;
|
||||||
|
|
||||||
|
// Инициализация отображения ошибки
|
||||||
|
input.classList.add('error');
|
||||||
|
|
||||||
|
// Фокусировка на поле ввода
|
||||||
|
input.focus();
|
||||||
|
}
|
||||||
|
}, 1000)();
|
||||||
|
};
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block js %}
|
||||||
|
<script type="text/javascript" src="/js/session.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/password.js"></script>
|
||||||
|
{% endblock %}
|
|
@ -1,410 +0,0 @@
|
||||||
{% block css %}
|
|
||||||
<link type="text/css" rel="stylesheet" href="/css/account.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/css/icons/arrow_right.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/css/icons/nametag.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/css/icons/keyhole.css">
|
|
||||||
<link type="text/css" rel="stylesheet" href="/css/icons/user_add.css">
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block body %}
|
|
||||||
<div class="column">
|
|
||||||
<section id="entry" class="panel medium">
|
|
||||||
<section class="header unselectable">
|
|
||||||
<h1>Идентификация</h1>
|
|
||||||
</section>
|
|
||||||
<section class="body">
|
|
||||||
<label id="login">
|
|
||||||
<i class="nametag"></i>
|
|
||||||
<input name="login" type="text" placeholder="Входной псевдоним"
|
|
||||||
value="{{ account.login ?? session.buffer.login ?? cookie.buffer_login }}"
|
|
||||||
onkeypress="if (event.keyCode === 13) __login()" autofocus>
|
|
||||||
<button class="accept" onclick="__login()"><i class="arrow right"></i></button>
|
|
||||||
</label>
|
|
||||||
<label id="password" class="hidden">
|
|
||||||
<i class="keyhole"></i>
|
|
||||||
<input name="password" type="password" placeholder="Пароль" autocomplete="current-password"
|
|
||||||
onkeypress="if (event.keyCode === 13) __password()">
|
|
||||||
<button class="accept" onclick="__password()"><i class="arrow right"></i></button>
|
|
||||||
</label>
|
|
||||||
<label id="invite" class="hidden">
|
|
||||||
<i class="user add"></i>
|
|
||||||
<input name="invite" type="text" placeholder="Ключ приглашения"
|
|
||||||
onkeypress="if (event.keyCode === 13) __invite()">
|
|
||||||
<button class="accept" onclick="__invite()"><i class="arrow right"></i></button>
|
|
||||||
</label>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="errors" class="panel medium animation window hidden" style="--height: 300px">
|
|
||||||
<section class="body">
|
|
||||||
<dl></dl>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
<section id="mnemonic" class="panel small hidden">
|
|
||||||
<section class="header unselectable">
|
|
||||||
<h2>Мнемонические</h2>
|
|
||||||
</section>
|
|
||||||
<section class="body">
|
|
||||||
<ul></ul>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<section id="classic" class="panel small hidden">
|
|
||||||
<section class="header unselectable">
|
|
||||||
<h2>Классические</h2>
|
|
||||||
</section>
|
|
||||||
<section class="body">
|
|
||||||
<ul></ul>
|
|
||||||
</section>
|
|
||||||
</section>
|
|
||||||
<script>
|
|
||||||
// Инициализация функций в глобальной области видимости
|
|
||||||
let _login, __login, _password, __password, _invite, __invite, _errors;
|
|
||||||
|
|
||||||
document.addEventListener('damper.initialized', function (e) {
|
|
||||||
// Инициализирован демпфер
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента с блоком входа в аккаунт
|
|
||||||
const entry = {wrap: document.getElementById('entry')};
|
|
||||||
entry.title = entry.wrap.getElementsByTagName('h1')[0];
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента с блоком мнемонических паролей
|
|
||||||
const mnemonic = {wrap: document.getElementById('mnemonic')};
|
|
||||||
mnemonic.list = mnemonic.wrap.getElementsByTagName('ul')[0];
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента с блоком классических паролей
|
|
||||||
const classic = {wrap: document.getElementById('classic')};
|
|
||||||
classic.list = classic.wrap.getElementsByTagName('ul')[0];
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента с блоком ошибок
|
|
||||||
const errors = {wrap: document.getElementById('errors')};
|
|
||||||
errors.list = errors.wrap.getElementsByTagName('dl')[0];
|
|
||||||
|
|
||||||
// Инициализация HTML-элементов-оболочек полей ввода
|
|
||||||
const labels = {
|
|
||||||
login: {label: document.getElementById('login')},
|
|
||||||
password: {label: document.getElementById('password')},
|
|
||||||
invite: {label: document.getElementById('invite')}
|
|
||||||
};
|
|
||||||
labels.login.input = labels.login.label.querySelector('input[name=login]');
|
|
||||||
labels.password.input = labels.password.label.querySelector('input[name=password]');
|
|
||||||
labels.invite.input = labels.invite.label.querySelector('input[name=invite]');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить входной псевдоним на сервер
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
_login = e.detail.damper(async () => {
|
|
||||||
// Деинициализация индикатора и анимации об ошибке
|
|
||||||
labels.login.input.classList.remove('error');
|
|
||||||
|
|
||||||
// Запрос к серверу
|
|
||||||
const response = await session.login(labels.login.input.value);
|
|
||||||
|
|
||||||
if (_errors(response.errors)) {
|
|
||||||
// Сгенерированы ошибки
|
|
||||||
|
|
||||||
// Разлокировка поля ввода
|
|
||||||
labels.login.input.disabled = false;;
|
|
||||||
|
|
||||||
// Инициализация отображения ошибки
|
|
||||||
labels.login.input.classList.add('error');
|
|
||||||
|
|
||||||
// Фокусировка на поле ввода
|
|
||||||
labels.login.input.focus();
|
|
||||||
} else {
|
|
||||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
|
||||||
|
|
||||||
if (typeof response.exist === 'boolean')
|
|
||||||
if (response.exist) {
|
|
||||||
// Найден аккаунт
|
|
||||||
|
|
||||||
// Инициализация интерфейса аутентификации
|
|
||||||
entry.title.innerText = 'Аутентификация';
|
|
||||||
labels.login.label.classList.add('hidden');
|
|
||||||
labels.password.label.classList.remove('hidden');
|
|
||||||
labels.password.input.focus();
|
|
||||||
} else {
|
|
||||||
// Не найден аккаунт
|
|
||||||
|
|
||||||
// Инициализация интерфейса регистрации
|
|
||||||
entry.title.innerText = 'Регистрация';
|
|
||||||
labels.login.label.classList.add('hidden');
|
|
||||||
labels.invite.label.classList.remove('hidden');
|
|
||||||
labels.invite.input.focus();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить входной псевдоним на сервер с дополнительной подготовкой
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
__login = () => {
|
|
||||||
// Блокировка поля ввода
|
|
||||||
labels.login.input.disabled = true;
|
|
||||||
|
|
||||||
// Реинициализация блока ошибок
|
|
||||||
_errors();
|
|
||||||
|
|
||||||
// Запуск процесса отправки входного псевдонима
|
|
||||||
_login();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить пароль на сервер
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
_password = e.detail.damper(async () => {
|
|
||||||
// Деинициализация индикатора и анимации об ошибке
|
|
||||||
labels.password.input.classList.remove('error');
|
|
||||||
|
|
||||||
// Запрос к серверу
|
|
||||||
const response = await session.password(labels.password.input.value);
|
|
||||||
|
|
||||||
if (_errors(response.errors)) {
|
|
||||||
// Сгенерированы ошибки
|
|
||||||
|
|
||||||
// Разлокировка поля ввода
|
|
||||||
labels.password.input.disabled = false;
|
|
||||||
|
|
||||||
// Инициализация отображения ошибки
|
|
||||||
labels.password.input.classList.add('error');
|
|
||||||
|
|
||||||
// Фокусировка на поле ввода
|
|
||||||
labels.password.input.focus();
|
|
||||||
} else {
|
|
||||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
|
||||||
|
|
||||||
if (typeof response.verify === 'boolean')
|
|
||||||
if (response.verify) {
|
|
||||||
// Пройдена проверка пароля на соответствие требованиям
|
|
||||||
|
|
||||||
if (response.account) {
|
|
||||||
// Инициализирован аккаунт
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить пароль на сервер с дополнительной подготовкой
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
__password = () => {
|
|
||||||
// Блокировка поля ввода
|
|
||||||
labels.password.input.disabled = true;
|
|
||||||
|
|
||||||
// Реинициализация блока ошибок
|
|
||||||
_errors();
|
|
||||||
|
|
||||||
// Запуск процесса отправки входного псевдонима
|
|
||||||
_password();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить код приглашения на сервер
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
_invite = e.detail.damper(async () => {
|
|
||||||
// Деинициализация индикатора и анимации об ошибке
|
|
||||||
labels.invite.input.classList.remove('error');
|
|
||||||
|
|
||||||
// Запрос к серверу
|
|
||||||
const response = await session.invite(labels.invite.input.value);
|
|
||||||
|
|
||||||
if (_errors(response.errors)) {
|
|
||||||
// Сгенерированы ошибки
|
|
||||||
|
|
||||||
// Разблокировка поля ввода
|
|
||||||
labels.invite.input.disabled = false;
|
|
||||||
|
|
||||||
// Инициализация отображения ошибки
|
|
||||||
labels.invite.input.classList.add('error');
|
|
||||||
|
|
||||||
// Фокусировка на поле ввода
|
|
||||||
labels.invite.input.focus();
|
|
||||||
} else {
|
|
||||||
// Не сгенерированы ошибки (подразумевается их отсутствие)
|
|
||||||
|
|
||||||
if (typeof response.exist === 'boolean')
|
|
||||||
if (response.exist) {
|
|
||||||
// Найдено приглашение
|
|
||||||
|
|
||||||
// Инициализация интерфейса ввода пароля
|
|
||||||
labels.invite.label.classList.add('hidden');
|
|
||||||
labels.password.input.autocomplete = 'new-password';
|
|
||||||
labels.password.label.classList.remove('hidden');
|
|
||||||
mnemonic.wrap.classList.remove('hidden');
|
|
||||||
classic.wrap.classList.remove('hidden');
|
|
||||||
|
|
||||||
for (let i = 0; i < 6; i++) {
|
|
||||||
// Генерация HTML-элементов с примерами мнемонических паролей
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента
|
|
||||||
const element = document.createElement('li');
|
|
||||||
|
|
||||||
// Генерация пароля
|
|
||||||
password.generate(Math.floor(Math.random() * 3) + 2, 'mnemonic')
|
|
||||||
.then((responce) => {
|
|
||||||
if (responce.password === '') {
|
|
||||||
// Не удалось сгенерировать пароль
|
|
||||||
|
|
||||||
// Перезапуск итерации
|
|
||||||
--i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запись пароля
|
|
||||||
element.innerText = responce.password;
|
|
||||||
|
|
||||||
// Запись HTML-элемента с паролем в список
|
|
||||||
mnemonic.list.appendChild(element);
|
|
||||||
|
|
||||||
// Генерация списка с текстом ошибок
|
|
||||||
_errors(response.errors);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < 8; i++) {
|
|
||||||
// Генерация HTML-элементов с примерами классических паролей
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента
|
|
||||||
const element = document.createElement('li');
|
|
||||||
|
|
||||||
// Генерация пароля
|
|
||||||
password.generate(Math.floor(Math.random() * 14) + 4)
|
|
||||||
.then((responce) => {
|
|
||||||
if (responce.password === '') {
|
|
||||||
// Не удалось сгенерировать пароль
|
|
||||||
|
|
||||||
// Перезапуск итерации
|
|
||||||
--i;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Запись пароля
|
|
||||||
element.innerText = responce.password;
|
|
||||||
|
|
||||||
// Запись HTML-элемента с паролем в список
|
|
||||||
classic.list.appendChild(element);
|
|
||||||
|
|
||||||
// Генерация списка с текстом ошибок
|
|
||||||
_errors(response.errors);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, 500);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Отправить код приглашения на сервер с дополнительной подготовкой
|
|
||||||
*
|
|
||||||
* @return {void}
|
|
||||||
*/
|
|
||||||
__invite = () => {
|
|
||||||
// Блокировка поля ввода
|
|
||||||
labels.invite.input.disabled = true;
|
|
||||||
|
|
||||||
// Реинициализация блока ошибок
|
|
||||||
_errors();
|
|
||||||
|
|
||||||
// Запуск процесса отправки входного псевдонима
|
|
||||||
_invite();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Сгенерировать HTML-элемент с блоком ошибок
|
|
||||||
*
|
|
||||||
* @param {object} registry Реестр ошибок
|
|
||||||
* @param {bool} reinitialization Реинициализировать?
|
|
||||||
*
|
|
||||||
* @return {bool} Сгенерированы ошибки?
|
|
||||||
*/
|
|
||||||
_errors = (registry, reinitialization = true) => {
|
|
||||||
function height() {
|
|
||||||
// Скрытие HTML-элемента
|
|
||||||
errors.wrap.style.zIndex = '-99999';
|
|
||||||
errors.wrap.style.left = '-99999px';
|
|
||||||
errors.wrap.style.top = '-99999px';
|
|
||||||
errors.wrap.style.position = 'absolute';
|
|
||||||
errors.wrap.style.display = 'var(--display, unset)';
|
|
||||||
errors.wrap.style.opacity = '0';
|
|
||||||
errors.wrap.style.animationName = 'unset';
|
|
||||||
|
|
||||||
// Реинициализация переменной с данными о высоте HTML-элемента
|
|
||||||
errors.wrap.style.setProperty('--height', errors.wrap.offsetHeight + 'px');
|
|
||||||
|
|
||||||
// Отмена скрытия HTML-элемента
|
|
||||||
errors.wrap.style.zIndex =
|
|
||||||
errors.wrap.style.left =
|
|
||||||
errors.wrap.style.top =
|
|
||||||
errors.wrap.style.position =
|
|
||||||
errors.wrap.style.display =
|
|
||||||
errors.wrap.style.opacity =
|
|
||||||
errors.wrap.style.animationName = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Удаление ошибок из прошлой генерации
|
|
||||||
if (reinitialization) errors.list.innerHTML = null;
|
|
||||||
|
|
||||||
for (error in registry) {
|
|
||||||
// Генерация HTML-элементов с текстами ошибок
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента
|
|
||||||
const element = document.createElement('dd');
|
|
||||||
|
|
||||||
if (typeof registry[error] === 'object') {
|
|
||||||
// Категория ошибок
|
|
||||||
|
|
||||||
// Проверка наличия ошибок
|
|
||||||
if (registry[error].length === 0) continue;
|
|
||||||
|
|
||||||
// Инициализация HTML-элемента
|
|
||||||
const element = document.createElement('dt');
|
|
||||||
|
|
||||||
// Запись текста категории
|
|
||||||
element.innerText = error;
|
|
||||||
|
|
||||||
// Запись HTML-элемента в список
|
|
||||||
errors.list.appendChild(element);
|
|
||||||
|
|
||||||
// Реинициализация высоты
|
|
||||||
height();
|
|
||||||
|
|
||||||
// Обработка вложенных ошибок (вход в рекурсию)
|
|
||||||
_errors(registry[error], false);
|
|
||||||
} else {
|
|
||||||
// Текст ошибки (подразумевается)
|
|
||||||
|
|
||||||
// Запись текста ошибки
|
|
||||||
element.innerText = registry[error];
|
|
||||||
|
|
||||||
// Запись HTML-элемента в список
|
|
||||||
errors.list.appendChild(element);
|
|
||||||
|
|
||||||
// Реинициализация высоты
|
|
||||||
height();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Реинициализация HTML-элемента с текстом ошибок
|
|
||||||
if (reinitialization && errors.list.childElementCount === 0) errors.wrap.classList.add('hidden');
|
|
||||||
else errors.wrap.classList.remove('hidden');
|
|
||||||
|
|
||||||
return errors.list.childElementCount === 0 ? false : true;
|
|
||||||
};
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
{% endblock %}
|
|
||||||
|
|
||||||
{% block js %}
|
|
||||||
<script type="text/javascript" src="/js/session.js"></script>
|
|
||||||
<script type="text/javascript" src="/js/password.js"></script>
|
|
||||||
{% endblock %}
|
|
|
@ -5,10 +5,6 @@ declare(strict_types=1);
|
||||||
namespace mirzaev\site\account\views;
|
namespace mirzaev\site\account\views;
|
||||||
|
|
||||||
// Файлы проекта
|
// Файлы проекта
|
||||||
use mirzaev\site\account\models\session,
|
|
||||||
mirzaev\site\account\models\account;
|
|
||||||
|
|
||||||
// Фреймворк PHP
|
|
||||||
use mirzaev\minimal\controller;
|
use mirzaev\minimal\controller;
|
||||||
|
|
||||||
// Шаблонизатор представлений
|
// Шаблонизатор представлений
|
||||||
|
@ -41,15 +37,13 @@ final class templater extends controller implements ArrayAccess
|
||||||
*
|
*
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(?session &$session = null, ?account &$account = null)
|
public function __construct()
|
||||||
{
|
{
|
||||||
// Инициализация шаблонизатора
|
// Инициализация шаблонизатора
|
||||||
$this->twig = new twig(new FilesystemLoader(VIEWS));
|
$this->twig = new twig(new FilesystemLoader(VIEWS));
|
||||||
|
|
||||||
// Инициализация глобальных переменных
|
// Инициализация глобальных переменных
|
||||||
$this->twig->addGlobal('cookie', $_COOKIE);
|
$this->twig->addGlobal('cookie', $_COOKIE);
|
||||||
if (isset($session->document)) $this->twig->addGlobal('session', $session);
|
|
||||||
if (isset($account->document)) $this->twig->addGlobal('account', $account);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue