разработана аутентификация и регистрация аккаунта
This commit is contained in:
		| @@ -1,74 +0,0 @@ | ||||
| <?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,18 +6,7 @@ namespace mirzaev\site\account\controllers; | ||||
|  | ||||
| // Файлы проекта | ||||
| use mirzaev\site\account\controllers\core, | ||||
|   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; | ||||
|   mirzaev\site\account\models\account as model; | ||||
|  | ||||
| /** | ||||
|  * Контроллер аккаунта | ||||
| @@ -36,114 +25,4 @@ final class account extends core | ||||
|   { | ||||
|     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") | ||||
|       }; | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $this->errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
|   | ||||
| @@ -5,20 +5,16 @@ declare(strict_types=1); | ||||
| namespace mirzaev\site\account\controllers; | ||||
|  | ||||
| // Файлы проекта | ||||
| use mirzaev\site\account\views\templater; | ||||
| use mirzaev\site\account\models\core as models; | ||||
| use mirzaev\site\account\models\account; | ||||
| use mirzaev\site\account\models\session; | ||||
|  | ||||
| // Библиотека для ArangoDB | ||||
| use ArangoDBClient\Document as _document; | ||||
| use mirzaev\site\account\views\templater, | ||||
|   mirzaev\site\account\models\core as models, | ||||
|   mirzaev\site\account\models\account, | ||||
|   mirzaev\site\account\models\session; | ||||
|  | ||||
| // Фреймворк PHP | ||||
| use mirzaev\minimal\controller; | ||||
|  | ||||
| // Фреймворк ВКонтакте | ||||
| use mirzaev\vk\core as vk; | ||||
| use mirzaev\vk\robots\user as robot; | ||||
| // Встроенные библиотеки | ||||
| use exception; | ||||
|  | ||||
| /** | ||||
|  * Ядро контроллеров | ||||
| @@ -28,11 +24,6 @@ use mirzaev\vk\robots\user as robot; | ||||
|  */ | ||||
| class core extends controller | ||||
| { | ||||
|   /** | ||||
|    * Переменные окружения | ||||
|    */ | ||||
|   protected robot $vk; | ||||
|  | ||||
|   /** | ||||
|    * Инстанция сессии | ||||
|    */ | ||||
| @@ -59,45 +50,46 @@ class core extends controller | ||||
|   /** | ||||
|    * Конструктор | ||||
|    * | ||||
|    * @return void | ||||
|    * @param bool $initialize Инициализировать контроллер? | ||||
|    */ | ||||
|   public function __construct() | ||||
|   public function __construct(bool $initialize = true) | ||||
|   { | ||||
|     parent::__construct(); | ||||
|     parent::__construct($initialize); | ||||
|  | ||||
|     // Инициализация ядра моделей (соединение с базой данных...) | ||||
|     new models(); | ||||
|     if ($initialize) { | ||||
|       // Запрошена инициализация | ||||
|  | ||||
|     // Инициализация шаблонизатора представлений | ||||
|     $this->view = new templater; | ||||
|       // Инициализация ядра моделей (соединение с базой данных...) | ||||
|       new models(); | ||||
|  | ||||
|     // Инициализация даты до которой будет активна сессия | ||||
|     $expires = time() + 604800; | ||||
|       // Инициализация даты до которой будет активна сессия | ||||
|       $expires = time() + 604800; | ||||
|  | ||||
|     // Инициализация сессии (без журналирования) | ||||
|     $this->session = new session($_COOKIE["session"] ?? null, $expires) ?? | ||||
|       header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию'); | ||||
|       // Инициализация значения по умолчанию | ||||
|       $_COOKIE["session"] ??= null; | ||||
|  | ||||
|     if ($_COOKIE["session"] ?? null !== $this->session->hash) { | ||||
|       // Изменился хеш сессии (подразумевается, что сессия устарела) | ||||
|       // Инициализация сессии | ||||
|       $this->session = new session($_COOKIE["session"], $expires); | ||||
|  | ||||
|       // Запись хеша новой сессии | ||||
|       setcookie('session', $this->session->hash, [ | ||||
|         'expires' => $expires, | ||||
|         'domain' => 'mirzaev.sexy', | ||||
|         'path' => '/', | ||||
|         'secure' => true, | ||||
|         'httponly' => true, | ||||
|         'samesite' => 'strict' | ||||
|       ]); | ||||
|     } | ||||
|       if ($_COOKIE["session"] !== $this->session->hash) { | ||||
|         // Изменился хеш сессии (подразумевается, что сессия устарела) | ||||
|  | ||||
|     // Инициализация аккаунта (без журналирования) | ||||
|     $this->account = $this->session->account(); | ||||
|         // Запись хеша новой сессии | ||||
|         setcookie('session', $this->session->hash, [ | ||||
|           '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,68 +15,65 @@ use mirzaev\site\account\controllers\core; | ||||
|  */ | ||||
| final class hotline extends core | ||||
| { | ||||
|     /** | ||||
|      * Страница с бегущей строкой | ||||
|      * | ||||
|      * Можно использовать совместно с элементом <iframe> для изоляции | ||||
|      * содержимого бегущей строки от поисковых роботов | ||||
|      * | ||||
|      * @param array $parameters | ||||
|      */ | ||||
|     public function index(array $parameters = []): ?string | ||||
|     { | ||||
|         // Инициализация элементов для генерации в головном элементе | ||||
|         $this->variables['head'] = [ | ||||
|             'title' => 'Бегущая строка', | ||||
|             'metas' => [ | ||||
|                 [ | ||||
|                     'attributes' => [ | ||||
|                         'name' => 'robots', | ||||
|                         'content' => 'nofollow' | ||||
|                     ] | ||||
|                 ] | ||||
|             ] | ||||
|         ]; | ||||
|   /** | ||||
|    * Страница с бегущей строкой | ||||
|    * | ||||
|    * Можно использовать совместно с элементом <iframe> для изоляции | ||||
|    * содержимого бегущей строки от поисковых роботов | ||||
|    * | ||||
|    * @param array $parameters | ||||
|    */ | ||||
|   public function index(array $parameters = []): ?string | ||||
|   { | ||||
|     // Инициализация элементов для генерации в головном элементе | ||||
|     $this->variables['head'] = [ | ||||
|       'title' => 'Бегущая строка', | ||||
|       'metas' => [ | ||||
|         [ | ||||
|           'attributes' => [ | ||||
|             'name' => 'robots', | ||||
|             'content' => 'nofollow' | ||||
|           ] | ||||
|         ] | ||||
|       ] | ||||
|     ]; | ||||
|  | ||||
|         // Инициализация бегущей строки | ||||
|         $this->variables['hotline'] = [ | ||||
|             'id' => $this->variables['request']['id'] ?? 'hotline' | ||||
|         ]; | ||||
|     // Инициализация бегущей строки | ||||
|     $this->variables['hotline'] = [ | ||||
|       'id' => $this->variables['request']['id'] ?? 'hotline' | ||||
|     ]; | ||||
|  | ||||
|         // Инициализация параметров бегущей строки | ||||
|         $this->variables['hotline']['parameters'] = [ | ||||
|             // 'step' => 2 | ||||
|         ]; | ||||
|     // Инициализация параметров бегущей строки | ||||
|     $this->variables['hotline']['parameters'] = [ | ||||
|       // 'step' => 2 | ||||
|     ]; | ||||
|  | ||||
|         // Инициализация аттрибутов бегущей строки | ||||
|         $this->variables['hotline']['attributes'] = [ | ||||
|     // Инициализация аттрибутов бегущей строки | ||||
|     $this->variables['hotline']['attributes'] = []; | ||||
|  | ||||
|         ]; | ||||
|  | ||||
|         // Инициализация элементов бегущей строки | ||||
|         $this->variables['hotline']['elements'] = [ | ||||
|             ['content' => '1'], | ||||
|             [ | ||||
|                 'tag' => 'article', | ||||
|                 'content' => '2' | ||||
|             ], | ||||
|             ['content' => '3'], | ||||
|             ['content' => '4'], | ||||
|             ['content' => '5'], | ||||
|             ['content' => '6'], | ||||
|             ['content' => '7'], | ||||
|             ['content' => '8'], | ||||
|             ['content' => '9'], | ||||
|             ['content' => '10'], | ||||
|             ['content' => '11'], | ||||
|             ['content' => '12'], | ||||
|             ['content' => '13'], | ||||
|             ['content' => '14'], | ||||
|             ['content' => '15'] | ||||
|         ]; | ||||
|  | ||||
|         // Генерация представления | ||||
|         return $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'index.html', $this->variables); | ||||
|     } | ||||
|     // Инициализация элементов бегущей строки | ||||
|     $this->variables['hotline']['elements'] = [ | ||||
|       ['content' => '1'], | ||||
|       [ | ||||
|         'tag' => 'article', | ||||
|         'content' => '2' | ||||
|       ], | ||||
|       ['content' => '3'], | ||||
|       ['content' => '4'], | ||||
|       ['content' => '5'], | ||||
|       ['content' => '6'], | ||||
|       ['content' => '7'], | ||||
|       ['content' => '8'], | ||||
|       ['content' => '9'], | ||||
|       ['content' => '10'], | ||||
|       ['content' => '11'], | ||||
|       ['content' => '12'], | ||||
|       ['content' => '13'], | ||||
|       ['content' => '14'], | ||||
|       ['content' => '15'] | ||||
|     ]; | ||||
|  | ||||
|     // Генерация представления | ||||
|     return $this->view->render(DIRECTORY_SEPARATOR . 'hotline' . DIRECTORY_SEPARATOR . 'index.html', $this->variables); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -24,7 +24,9 @@ final class index extends core | ||||
|   { | ||||
|     // Инициализация узлов | ||||
|     $this->view->nodes = [ | ||||
|       'account' => $this->view->render(DIRECTORY_SEPARATOR . 'nodes' . DIRECTORY_SEPARATOR . (isset($this->account) ? 'profile.html' : 'authentication.html')) | ||||
|       'account' => $this->view->render(DIRECTORY_SEPARATOR . (isset($this->account->document) | ||||
|         ? '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')) */ | ||||
|     ]; | ||||
|  | ||||
|   | ||||
| @@ -24,16 +24,16 @@ final class session extends core | ||||
|   use errors; | ||||
|  | ||||
|   /** | ||||
|    * Записать входной псевдоним  | ||||
|    * Записать входной псевдоним в буфер сессии | ||||
|    * | ||||
|    * Проверяет существование аккаунта с этим входным псевдонимом | ||||
|    * и запоминает для использования в процессе аутентификации | ||||
|    * | ||||
|    * @param array $parameters Параметры запроса | ||||
|    *  | ||||
|    * @return string JSON-документ с запрашиваемыми параметрами | ||||
|    * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами | ||||
|    */ | ||||
|   public function login(array $parameters = []): string | ||||
|   public function login(array $parameters = []): void | ||||
|   { | ||||
|     // Инициализация буфера ответа | ||||
|     $buffer = []; | ||||
| @@ -51,20 +51,27 @@ final class session extends core | ||||
|       // Проверка параметров на соответствование требованиям | ||||
|       if ($length === 0) throw new exception('Входной псевдоним не может быть пустым'); | ||||
|       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::read($parameters['login'], $this->errors['account']); | ||||
|       $account = account::login($parameters['login']); | ||||
|  | ||||
|       // Генерация ответа по запрашиваемым параметрам | ||||
|       foreach ($return as $parameter) match ($parameter) { | ||||
|         'exist' => $buffer['exist'] = isset($account->instance), | ||||
|         'exist' => $buffer['exist'] = isset($account->document), | ||||
|         '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, | ||||
|         default => throw new exception("Параметр не найден: $parameter") | ||||
|       }; | ||||
|  | ||||
|       if ($parameters['remember'] === '1') $this->session->remember('account.identification.login', $parameters['login']); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $this->errors['session'][] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -76,23 +83,40 @@ final class session extends core | ||||
|     // Запись реестра ошибок в буфер ответа | ||||
|     if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); | ||||
|  | ||||
|     // Запись заголовка ответа | ||||
|     // Запись заголовков ответа | ||||
|     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 Параметры запроса | ||||
|    *  | ||||
|    * @return string JSON-документ с запрашиваемыми параметрами | ||||
|    * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами | ||||
|    */ | ||||
|   public function password(array $parameters = []): string | ||||
|   public function password(array $parameters = []): void | ||||
|   { | ||||
|     // Инициализация буфера ответа | ||||
|     $buffer = []; | ||||
| @@ -101,57 +125,74 @@ final class session extends core | ||||
|     $return = explode(',', $parameters['return'], 50); | ||||
|  | ||||
|     try { | ||||
|       // Проверка наличия обязательных параметров | ||||
|       if (empty($parameters['password'])) throw new exception('Необходимо передать пароль'); | ||||
|  | ||||
|       // Вычисление длины | ||||
|       $length = strlen($parameters['password']); | ||||
|  | ||||
|       // Проверка параметров на соответствование требованиям | ||||
|       if ($length === 0) throw new exception('Пароль не может быть пустым'); | ||||
|       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) { | ||||
|         '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, | ||||
|         default => throw new exception("Параметр не найден: $parameter") | ||||
|       }; | ||||
|  | ||||
|       if ($parameters['remember'] === '1') throw new exception('Запоминать пароль не безопасно'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $this->errors['session'][] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
|         'line' => $e->getLine(), | ||||
|         '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); | ||||
|  | ||||
|     // Запись заголовка ответа | ||||
|     // Запись заголовков ответа | ||||
|     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 Параметры запроса | ||||
|    *  | ||||
|    * @return string JSON-документ с запрашиваемыми параметрами | ||||
|    * @return void В буфер вывода JSON-документ с запрашиваемыми параметрами | ||||
|    */ | ||||
|   public function invite(array $parameters = []): string | ||||
|   public function invite(array $parameters = []): void | ||||
|   { | ||||
|     // Инициализация буфера ответа | ||||
|     $buffer = []; | ||||
| @@ -168,22 +209,29 @@ final class session extends core | ||||
|  | ||||
|       // Проверка параметров на соответствование требованиям | ||||
|       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'], $this->errors['session']); | ||||
|       $invite = invite::read($parameters['invite']); | ||||
|  | ||||
|       // Генерация ответа по запрашиваемым параметрам | ||||
|       foreach ($return as $parameter) match ($parameter) { | ||||
|         'exist' => $buffer['exist'] = isset($invite->instance), | ||||
|         'exist' => $buffer['exist'] = isset($invite->document), | ||||
|         // from временное решение пока не будет разработана система сессий | ||||
|         'from' => $return['from'] = ['login' => 'mirzaev'] ?? $invite->from(), | ||||
|         'from' => $buffer['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, | ||||
|         default => throw new exception("Параметр не найден: $parameter") | ||||
|       }; | ||||
|  | ||||
|       if ($parameters['remember'] === '1') $this->session->remember('account.registration.invite', $parameters['invite']); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $this->errors['session'][] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -195,9 +243,26 @@ final class session extends core | ||||
|     // Запись реестра ошибок в буфер ответа | ||||
|     if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors); | ||||
|  | ||||
|     // Запись заголовка ответа | ||||
|     // Запись заголовков ответа | ||||
|     header('Content-Type: application/json'); | ||||
|    | ||||
|     return json_encode($buffer); | ||||
|     header('Content-Encoding: none'); | ||||
|     header('X-Accel-Buffering: no'); | ||||
|  | ||||
|     // Инициализация буфера вывода | ||||
|     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']; | ||||
|       else if (is_array($error)) $buffer[$offset] = static::parse_only_text($error); | ||||
|       else if (is_array($error) && count($error) > 0) $buffer[$offset] = static::parse_only_text($error); | ||||
|     } | ||||
|  | ||||
|     return $buffer; | ||||
|   | ||||
| @@ -4,9 +4,6 @@ declare(strict_types=1); | ||||
|  | ||||
| namespace mirzaev\site\account\models; | ||||
|  | ||||
| // Файлы проекта | ||||
| use mirzaev\site\account\models\vk; | ||||
|  | ||||
| // Фреймворк ArangoDB | ||||
| use mirzaev\arangodb\collection, | ||||
|   mirzaev\arangodb\document; | ||||
| @@ -31,19 +28,162 @@ final class account extends core | ||||
|   public const COLLECTION = 'account'; | ||||
|  | ||||
|   /** | ||||
|    * Инстанция в базе данных | ||||
|    * Инстанция документа аккаунта в базе данных | ||||
|    */ | ||||
|   public ?_document $instance; | ||||
|   public ?_document $document; | ||||
|  | ||||
|   /** | ||||
|    * Прочитать | ||||
|    * Конструктор | ||||
|    * | ||||
|    * 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 array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?self Инстанция аккаунта, если найден | ||||
|    * @return ?self Инстанция аккаунта, если аутентифицирован | ||||
|    */ | ||||
|   public static function read(string $login, array &$errors = []): ?self | ||||
|   public static function login(string $login, array &$errors = []): ?self | ||||
|   { | ||||
|     try { | ||||
|       if (collection::init(static::$db->session, self::COLLECTION)) { | ||||
| @@ -52,26 +192,25 @@ final class account extends core | ||||
|         // Инициализация инстанции аккаунта | ||||
|         $instance = new self; | ||||
|  | ||||
|         // Поиск аккаунта | ||||
|         $instance->instance = collection::search( | ||||
|         // Поиск инстанции аккаунта в базе данных | ||||
|         $instance->document = collection::search( | ||||
|           static::$db->session, | ||||
|           sprintf( | ||||
|             <<<AQL | ||||
|             <<<'AQL' | ||||
|               FOR d IN %s | ||||
|               FILTER d.login == '%s' | ||||
|               RETURN d | ||||
|                 FILTER d.login == '%s' | ||||
|                 RETURN d | ||||
|             AQL, | ||||
|             self::COLLECTION, | ||||
|             $login | ||||
|           ) | ||||
|         ); | ||||
|  | ||||
|         return $instance; | ||||
|       } | ||||
|  | ||||
|       throw new exception('Не удалось инициализировать коллекцию'); | ||||
|         if ($instance->document instanceof _document) return $instance; | ||||
|         else throw new exception('Не удалось найти инстанцию аккаунта в базе данных'); | ||||
|       } else throw new exception('Не удалось инициализировать коллекцию'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -86,75 +225,20 @@ final class account extends core | ||||
|   /** | ||||
|    * Создать | ||||
|    * | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array $data Данные аккаунта | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?_document Инстанция аккаунта, если удалось создать | ||||
|    * @return bool Создан аккаунт? | ||||
|    */ | ||||
|   public static function create(array &$errors = []): ?_document | ||||
|   public static function create(array $data = [], array &$errors = []): bool | ||||
|   { | ||||
|     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 | ||||
|         ))) { | ||||
|           // Найден созданный аккаунт | ||||
|  | ||||
|           return $account; | ||||
|         } else throw new exception('Не удалось создать аккаунт'); | ||||
|       } else throw new exception('Не удалось инициализировать коллекцию'); | ||||
|       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('Не удалось инициализировать коллекцию'); | ||||
|     } 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[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -167,56 +251,72 @@ final class account extends core | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Поиск связанного аккаунта ВКонтакте | ||||
|    * Записать | ||||
|    * | ||||
|    * @param _document $account Инстанция аккаунта | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * Записывает свойство в инстанцию документа аккаунта из базы данных | ||||
|    * | ||||
|    * @return ?_document Инстанция аккаунта, если удалось найти | ||||
|    * @param string $name Название | ||||
|    * @param mixed $value Содержимое | ||||
|    * | ||||
|    * @return void | ||||
|    */ | ||||
|   public static function vk(_document $account, array &$errors = []): ?_document | ||||
|   public function __set(string $name, mixed $value = null): void | ||||
|   { | ||||
|     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) | ||||
|       ) { | ||||
|         // Инициализирована коллекция | ||||
|     $this->document->{$name} = $value; | ||||
|   } | ||||
|  | ||||
|         if ($vk = collection::search(static::$db->session, sprintf( | ||||
|           <<<AQL | ||||
|                         FOR document IN %s | ||||
|                         LET edge = ( | ||||
|                             FOR edge IN %s | ||||
|                             FILTER edge._from == '%s' | ||||
|                             SORT edge._key DESC | ||||
|                             LIMIT 1 | ||||
|                             RETURN edge | ||||
|                         ) | ||||
|                         FILTER document._id == edge[0]._to | ||||
|                         LIMIT 1 | ||||
|                         RETURN document | ||||
|                     AQL, | ||||
|           vk::COLLECTION, | ||||
|           self::COLLECTION . '_edge_' . vk::COLLECTION, | ||||
|           $account->getId() | ||||
|         ))) { | ||||
|           // Найден аккаунт ВКонтакте | ||||
|   /** | ||||
|    * Прочитать | ||||
|    * | ||||
|    * Читает свойство из инстанции документа аккаунта из базы данных | ||||
|    * | ||||
|    * @param string $name Название | ||||
|    * | ||||
|    * @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных | ||||
|    */ | ||||
|   public function __get(string $name): mixed | ||||
|   { | ||||
|     return $this->document->{$name}; | ||||
|   } | ||||
|  | ||||
|           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() | ||||
|       ]; | ||||
|     } | ||||
|   /** | ||||
|    * Проверить инициализированность | ||||
|    * | ||||
|    * Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных | ||||
|    * | ||||
|    * @param string $name Название | ||||
|    * | ||||
|    * @return bool Свойство инициализировано? | ||||
|    */ | ||||
|   public function __isset(string $name): bool | ||||
|   { | ||||
|     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,18 +33,30 @@ class core extends model | ||||
|    */ | ||||
|   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) | ||||
|   { | ||||
|     if (isset($db)) { | ||||
|       // Получена инстанция соединения с базой данных | ||||
|     parent::__construct($initialize); | ||||
|  | ||||
|       // Запись и инициализация соединения с базой данных | ||||
|       $this->__set('db', $db); | ||||
|     } else { | ||||
|       // Не получена инстанция соединения с базой данных | ||||
|     if ($initialize) { | ||||
|       // Запрошена инициализация | ||||
|  | ||||
|       // Инициализация соединения с базой данных по умолчанию | ||||
|       $this->__get('db'); | ||||
|       if (isset($db)) { | ||||
|         // Получена инстанция соединения с базой данных | ||||
|  | ||||
|         // Запись и инициализация соединения с базой данных | ||||
|         $this->__set('db', $db); | ||||
|       } else { | ||||
|         // Не получена инстанция соединения с базой данных | ||||
|  | ||||
|         // Инициализация соединения с базой данных по умолчанию | ||||
|         $this->__get('db'); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   | ||||
| @@ -27,7 +27,7 @@ final class password extends core | ||||
|    * Сгенерировать мнемонический пароль | ||||
|    * | ||||
|    * @param int $length Длина (количество слов) | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?string Пароль | ||||
|    */ | ||||
| @@ -107,7 +107,7 @@ final class password extends core | ||||
|  | ||||
|       return implode(' ', $password); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -123,7 +123,7 @@ final class password extends core | ||||
|    * Сгенерировать классический пароль | ||||
|    * | ||||
|    * @param int $length Длина (количество символов) | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?string Пароль | ||||
|    */ | ||||
| @@ -145,7 +145,7 @@ final class password extends core | ||||
|  | ||||
|       return $password; | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
|   | ||||
| @@ -31,15 +31,15 @@ final class invite extends core | ||||
|   public const COLLECTION = 'invite'; | ||||
|  | ||||
|   /** | ||||
|    * Инстанция в базе данных | ||||
|    * Инстанция документа приглашения в базе данных | ||||
|    */ | ||||
|   public ?_document $instance; | ||||
|   public ?_document $document; | ||||
|  | ||||
|   /** | ||||
|    * Прочитать | ||||
|    * | ||||
|    * @param string $invite Ключ приглашения | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?self Инстанция приглашения, если оно найдено | ||||
|    */ | ||||
| @@ -53,25 +53,24 @@ final class invite extends core | ||||
|         $instance = new self; | ||||
|  | ||||
|         // Поиск приглашения | ||||
|         $instance->instance = collection::search( | ||||
|         $instance->document = collection::search( | ||||
|           static::$db->session, | ||||
|           sprintf( | ||||
|             <<<AQL | ||||
|               FOR d IN %s | ||||
|               FILTER d.key == '%s' && d.active == true | ||||
|               RETURN d | ||||
|                 FILTER d.key == '%s' && d.active == true | ||||
|                 RETURN d | ||||
|             AQL, | ||||
|             self::COLLECTION, | ||||
|             $invite | ||||
|           ) | ||||
|         ); | ||||
|  | ||||
|         return $instance; | ||||
|       } | ||||
|  | ||||
|       throw new exception('Не удалось инициализировать коллекцию'); | ||||
|         if ($instance->document instanceof _document) return $instance; | ||||
|         else throw new exception('Не удалось найти инстанцию приглашения в базе данных'); | ||||
|       } throw new exception('Не удалось инициализировать коллекцию'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -85,6 +84,6 @@ final class invite extends core | ||||
|  | ||||
|   public function from(): ?account | ||||
|   { | ||||
|     return new account(); | ||||
|     return null; | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -31,7 +31,7 @@ final class session extends core | ||||
|   public const COLLECTION = 'session'; | ||||
|  | ||||
|   /** | ||||
|    * Данные сессии из базы данных  | ||||
|    * Инстанция документа сессии в базе данных  | ||||
|    */ | ||||
|   public _document $document; | ||||
|  | ||||
| @@ -42,7 +42,7 @@ final class session extends core | ||||
|    * | ||||
|    * @param ?string $hash Хеш сессии в базе данных | ||||
|    * @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии) | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return static Инстанция сессии | ||||
|    */ | ||||
| @@ -55,8 +55,8 @@ final class session extends core | ||||
|         if (isset($hash) && $session = collection::search(static::$db->session, sprintf( | ||||
|           <<<AQL | ||||
|             FOR d IN %s | ||||
|             FILTER d.hash == '$hash' && d.expires > %d | ||||
|             RETURN d | ||||
|               FILTER d.hash == '$hash' && d.expires > %d && d.status == 'active' | ||||
|               RETURN d | ||||
|           AQL, | ||||
|           self::COLLECTION, | ||||
|           time() | ||||
| @@ -68,8 +68,8 @@ final class session extends core | ||||
|         } else if ($session = collection::search(static::$db->session, sprintf( | ||||
|           <<<AQL | ||||
|             FOR d IN %s | ||||
|             FILTER d.ip == '%s' && d.expires > %d | ||||
|             RETURN d | ||||
|               FILTER d.ip == '%s' && d.expires > %d && d.status == 'active' | ||||
|               RETURN d | ||||
|           AQL, | ||||
|           self::COLLECTION, | ||||
|           $_SERVER['REMOTE_ADDR'], | ||||
| @@ -84,20 +84,21 @@ final class session extends core | ||||
|  | ||||
|           // Запись сессии в базу данных | ||||
|           $_id = document::write(static::$db->session, self::COLLECTION, [ | ||||
|             'ip' => $_SERVER['REMOTE_ADDR'], | ||||
|             'expires' => $expires ?? time() + 604800 | ||||
|             'status' => 'active', | ||||
|             'expires' => $expires ?? time() + 604800, | ||||
|             'ip' => $_SERVER['REMOTE_ADDR'] | ||||
|           ]); | ||||
|  | ||||
|           if ($session = collection::search(static::$db->session, sprintf( | ||||
|             <<<AQL | ||||
|               FOR d IN %s | ||||
|               FILTER d._id == '$_id' && d.expires > %d | ||||
|               RETURN d | ||||
|                 FILTER d._id == '$_id' && d.expires > %d && d.status == 'active' | ||||
|                 RETURN d | ||||
|             AQL, | ||||
|             self::COLLECTION, | ||||
|             time() | ||||
|           ))) { | ||||
|             // Найдена созданная сессия | ||||
|             // Найдена только что созданная сессия | ||||
|  | ||||
|             // Запись хеша | ||||
|             $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); | ||||
| @@ -112,7 +113,7 @@ final class session extends core | ||||
|         } | ||||
|       } else throw new exception('Не удалось инициализировать коллекцию'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -128,14 +129,16 @@ final class session extends core | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Связь сессии с аккаунтом | ||||
|    * Инициализировать связб сессии с аккаунтом | ||||
|    * | ||||
|    * @param _document $account Инстанция аккаунта | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * Ищет связь сессии с аккаунтом, если не находит, то создаёт её | ||||
|    * | ||||
|    * @return bool Статус выполнения | ||||
|    * @param account $account Инстанция аккаунта | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return bool Связан аккаунт? | ||||
|    */ | ||||
|   public function connect(_document $account, array &$errors = []): bool | ||||
|   public function connect(account $account, array &$errors = []): bool | ||||
|   { | ||||
|     try { | ||||
|       if ( | ||||
| @@ -145,17 +148,30 @@ final class session extends core | ||||
|       ) { | ||||
|         // Инициализирована коллекция | ||||
|  | ||||
|         if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [ | ||||
|           '_from' => $this->document->getId(), | ||||
|           '_to' => $account->getId() | ||||
|         ])) { | ||||
|           // Создано ребро: session -> account | ||||
|         if ( | ||||
|           collection::search(static::$db->session, sprintf( | ||||
|             <<<AQL | ||||
|               FOR document IN %s | ||||
|                 FILTER document._from == '%s' && document._to == '%s' | ||||
|                 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; | ||||
|         } else throw new exception('Не удалось создать ребро: session -> account'); | ||||
|       } else throw new exception('Не удалось инициализировать коллекцию'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -168,13 +184,13 @@ final class session extends core | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Поиск связанного аккаунта | ||||
|    * Найти связанный аккаунт | ||||
|    * | ||||
|    * @param array &$errors Журнал ошибок | ||||
|    * @param array &$errors Реестр ошибок | ||||
|    * | ||||
|    * @return ?_document Инстанция аккаунта, если удалось найти | ||||
|    * @return ?account Инстанция аккаунта, если удалось найти | ||||
|    */ | ||||
|   public function account(array &$errors = []): ?_document | ||||
|   public function account(array &$errors = []): ?account | ||||
|   { | ||||
|     try { | ||||
|       if ( | ||||
| @@ -184,31 +200,34 @@ 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 | ||||
|             FOR document IN %s | ||||
|             LET edge = ( | ||||
|               FOR edge IN %s | ||||
|               FILTER edge._from == '%s' | ||||
|               SORT edge._key DESC | ||||
|               LET edge = ( | ||||
|                 FOR edge IN %s | ||||
|                 FILTER edge._from == '%s' | ||||
|                 SORT edge._key DESC | ||||
|                 LIMIT 1 | ||||
|                 RETURN edge | ||||
|               ) | ||||
|               FILTER document._id == edge[0]._to | ||||
|               LIMIT 1 | ||||
|               RETURN edge | ||||
|             ) | ||||
|             FILTER document._id == edge[0]._to | ||||
|             LIMIT 1 | ||||
|             RETURN document | ||||
|               RETURN document | ||||
|           AQL, | ||||
|           account::COLLECTION, | ||||
|           self::COLLECTION . '_edge_' . account::COLLECTION, | ||||
|           $this->document->getId() | ||||
|         ))) { | ||||
|           // Найден аккаунт | ||||
|           $this->getId() | ||||
|         )); | ||||
|  | ||||
|           return $account; | ||||
|         } else throw new exception('Не удалось найти аккаунт'); | ||||
|         if ($account->document instanceof _document) return $account; | ||||
|         else throw new exception('Не удалось найти инстанцию аккаунта в базе данных'); | ||||
|       } else throw new exception('Не удалось инициализировать коллекцию'); | ||||
|     } catch (exception $e) { | ||||
|       // Запись в журнал ошибок | ||||
|       // Запись в реестр ошибок | ||||
|       $errors[] = [ | ||||
|         'text' => $e->getMessage(), | ||||
|         'file' => $e->getFile(), | ||||
| @@ -220,6 +239,47 @@ final class session extends core | ||||
|     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; | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|    * Записать | ||||
|    * | ||||
| @@ -287,6 +347,6 @@ final class session extends core | ||||
|    */ | ||||
|   public function __call(string $name, array $arguments = []) | ||||
|   { | ||||
|     if (method_exists($this, $name)) return $this->document->{$name}($arguments); | ||||
|     if (method_exists($this->document, $name)) return $this->document->{$name}($arguments); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -1,555 +0,0 @@ | ||||
| <?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,7 +19,14 @@ main { | ||||
|   align-items: unset; | ||||
| } | ||||
|  | ||||
| div.column { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   gap: 20px; | ||||
| } | ||||
|  | ||||
| section.panel { | ||||
|     --display     : flex; | ||||
|     z-index       : 1000; | ||||
|     width         : 400px; | ||||
|     position      : absolute; | ||||
| @@ -27,6 +34,10 @@ section.panel { | ||||
|     flex-direction: column; | ||||
| } | ||||
|  | ||||
| div.column>section.panel { | ||||
|     position      : unset; | ||||
| } | ||||
|  | ||||
| section.panel.medium { | ||||
|   width: 300px; | ||||
| } | ||||
| @@ -43,7 +54,6 @@ section.panel#classic { | ||||
|   margin-left: 570px; | ||||
| } | ||||
|  | ||||
|  | ||||
| section.panel>section.body>ul { | ||||
|   margin: 0 5%; | ||||
|   padding: 0; | ||||
| @@ -61,6 +71,34 @@ section.panel>section.body>ul>li { | ||||
|   animation-fill-mode      : forwards; | ||||
|   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 { | ||||
|     z-index           : 1000; | ||||
|     height            : 50px; | ||||
|   | ||||
| @@ -21,3 +21,44 @@ | ||||
|     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,5 +1,3 @@ | ||||
| @import url('/fonts/commissioner.ttf'); | ||||
|  | ||||
| @media (prefers-color-scheme: light) { | ||||
|     :root { | ||||
|         --background-above-1       : #fff; | ||||
| @@ -81,9 +79,8 @@ | ||||
|     user-select          : none; | ||||
| } | ||||
|  | ||||
| .hidden { | ||||
| .hidden:not(.animation) { | ||||
|   display: none !important; | ||||
|   opacity: 0; | ||||
| } | ||||
|  | ||||
| * { | ||||
|   | ||||
										
											Binary file not shown.
										
									
								
							| @@ -39,11 +39,7 @@ $router->write('/session/password', 'session', 'password', 'POST'); | ||||
| $router->write('/session/invite', 'session', 'invite', 'POST'); | ||||
|  | ||||
| // Инициализация ядра | ||||
| $core = new core(namespace: __NAMESPACE__, router: $router); | ||||
|  | ||||
| // Инициализация ядер | ||||
| $core->controller = new controller; | ||||
| $core->model = new model; | ||||
| $core = new core(namespace: __NAMESPACE__, router: $router, controller: new controller(false), model: new model(false)); | ||||
|  | ||||
| // Обработка запроса | ||||
| echo $core->start(); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ class password { | ||||
|    * @return {object} {(string) password, (array) errors} | ||||
|    */ | ||||
|   static async generate(length = 12, type = "classic") { | ||||
|     // Запрос к серверу | ||||
|     return await fetch("https://account.mirzaev.sexy/api/generate/password", { | ||||
|       method: "POST", | ||||
|       headers: { | ||||
|   | ||||
| @@ -6,12 +6,12 @@ class session { | ||||
|    * | ||||
|    * Записывает входной псевдоним в сессию, а так же проверяет существование аккаунта с ним | ||||
|    * | ||||
|    * @param {string} login Входной | ||||
|    * @param {string} login Входной псевдоним | ||||
|    * | ||||
|    * @return {object} {(bool) exist, (array) errors} | ||||
|    */ | ||||
|   static async login(login) { | ||||
|     // Запрос | ||||
|     // Запрос к серверу | ||||
|     return await fetch('https://account.mirzaev.sexy/session/login', { | ||||
|       method: 'POST', | ||||
|       headers: { | ||||
| @@ -32,16 +32,16 @@ class session { | ||||
|    * | ||||
|    * @param {string} password Пароль | ||||
|    * | ||||
|    * @return {object} {(bool) verify, (array) errors} | ||||
|    * @return {object} {(bool) verify, (bool) account, (array) errors} | ||||
|    */ | ||||
|   static async password(password) { | ||||
|     // Запрос | ||||
|     // Запрос к серверу | ||||
|     return await fetch('https://account.mirzaev.sexy/session/password', { | ||||
|       method: 'POST', | ||||
|       headers: { | ||||
|         'Content-Type': 'application/x-www-form-urlencoded', | ||||
|       }, | ||||
|       body: `password=${password}&return=verify,errors` | ||||
|       body: `password=${password}&remember=1&return=verify,account,errors` | ||||
|     }) | ||||
|       .then((response) => response.json()) | ||||
|       .then((data) => { | ||||
| @@ -59,7 +59,7 @@ class session { | ||||
|    * @return {object} {(bool) exist, (array) from, (array) errors} | ||||
|    */ | ||||
|   static async invite(invite) { | ||||
|     // Запрос | ||||
|     // Запрос к серверу | ||||
|     return await fetch("https://account.mirzaev.sexy/session/invite", { | ||||
|       method: "POST", | ||||
|       headers: { | ||||
| @@ -69,6 +69,16 @@ class session { | ||||
|     }) | ||||
|       .then((response) => response.json()) | ||||
|       .then((data) => { | ||||
|         if (data.exist === false) { | ||||
|           // Не найдено приглашение | ||||
|  | ||||
|           // Инициализация категории ошибок | ||||
|           if (typeof data.errors.session === 'undefined') data.errors.session = []; | ||||
|  | ||||
|           // Запись ошибки | ||||
|           data.errors.session.push('Не найдено приглашение'); | ||||
|         } | ||||
|  | ||||
|         return data; | ||||
|       }); | ||||
|   } | ||||
|   | ||||
| @@ -1,247 +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 %} | ||||
| <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 %} | ||||
							
								
								
									
										410
									
								
								mirzaev/site/account/system/views/pages/entry.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										410
									
								
								mirzaev/site/account/system/views/pages/entry.html
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,410 @@ | ||||
| {% 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,6 +5,10 @@ declare(strict_types=1); | ||||
| namespace mirzaev\site\account\views; | ||||
|  | ||||
| // Файлы проекта | ||||
| use mirzaev\site\account\models\session, | ||||
|   mirzaev\site\account\models\account; | ||||
|  | ||||
| // Фреймворк PHP | ||||
| use mirzaev\minimal\controller; | ||||
|  | ||||
| // Шаблонизатор представлений | ||||
| @@ -37,13 +41,15 @@ final class templater extends controller implements ArrayAccess | ||||
|    * | ||||
|    * @return void | ||||
|    */ | ||||
|   public function __construct() | ||||
|   public function __construct(?session &$session = null, ?account &$account = null) | ||||
|   { | ||||
|     // Инициализация шаблонизатора | ||||
|     $this->twig = new twig(new FilesystemLoader(VIEWS)); | ||||
|  | ||||
|     // Инициализация глобальных переменных | ||||
|     $this->twig->addGlobal('cookie', $_COOKIE); | ||||
|     if (isset($session->document)) $this->twig->addGlobal('session', $session); | ||||
|     if (isset($account->document)) $this->twig->addGlobal('account', $account); | ||||
|   } | ||||
|  | ||||
|   /** | ||||
|   | ||||
		Reference in New Issue
	
	Block a user