transition from site-mirzaev
							
								
								
									
										9
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						| @@ -1,8 +1,3 @@ | |||||||
| # ---> Composer | !.gitignore | ||||||
| composer.phar | composer.phar | ||||||
| /vendor/ | vendor | ||||||
|  |  | ||||||
| # Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control |  | ||||||
| # You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file |  | ||||||
| # composer.lock |  | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										57
									
								
								composer.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,57 @@ | |||||||
|  | { | ||||||
|  |     "name": "mirzaev/site-account", | ||||||
|  |     "description": "Мой персональный сайт", | ||||||
|  |     "readme": "README.md", | ||||||
|  |     "keywords": [ | ||||||
|  |         "site", | ||||||
|  |         "api", | ||||||
|  |         "authentication", | ||||||
|  |         "auth" | ||||||
|  |     ], | ||||||
|  |     "type": "site", | ||||||
|  |     "homepage": "https://git.mirzaev.sexy/mirzaev/site-account", | ||||||
|  |     "license": "WTFPL", | ||||||
|  |     "authors": [ | ||||||
|  |         { | ||||||
|  |             "name": "Arsen Mirzaev Tatyano-Muradovich", | ||||||
|  |             "email": "arsen@mirzaev.sexy", | ||||||
|  |             "homepage": "https://mirzaev.sexy", | ||||||
|  |             "role": "Programmer" | ||||||
|  |         } | ||||||
|  |     ], | ||||||
|  |     "support": { | ||||||
|  |         "email": "arsen@mirzaev.sexy", | ||||||
|  |         "wiki": "https://git.mirzaev.sexy/mirzaev/site-account/wiki", | ||||||
|  |         "issues": "https://git.mirzaev.sexy/mirzaev/site-account/issues" | ||||||
|  |     }, | ||||||
|  |     "funding": [ | ||||||
|  |         { | ||||||
|  |             "type": "funding", | ||||||
|  |             "url": "https://fund.mirzaev.sexy" | ||||||
|  |         } | ||||||
|  |     ], | ||||||
|  |     "require": { | ||||||
|  |         "php": "~8.1", | ||||||
|  |         "ext-sodium": "~8.1", | ||||||
|  |         "mirzaev/minimal": "^2.0.x-dev", | ||||||
|  |         "mirzaev/accounts": "~1.2.x-dev", | ||||||
|  |         "mirzaev/arangodb": "^1.0.0", | ||||||
|  |         "mirzaev/vk": "^5.0", | ||||||
|  |         "triagens/arangodb": "~3.9.x-dev", | ||||||
|  |         "twig/twig": "^3.4", | ||||||
|  |         "guzzlehttp/guzzle": "^7.5" | ||||||
|  |     }, | ||||||
|  |     "require-dev": { | ||||||
|  |         "phpunit/phpunit": "~9.5" | ||||||
|  |     }, | ||||||
|  |     "autoload": { | ||||||
|  |         "psr-4": { | ||||||
|  |             "mirzaev\\site\\account\\": "mirzaev/site/account/system" | ||||||
|  |         } | ||||||
|  |     }, | ||||||
|  |     "autoload-dev": { | ||||||
|  |         "psr-4": { | ||||||
|  |             "mirzaev\\site\\account\\tests\\": "mirzaev/site/account/tests" | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										3051
									
								
								composer.lock
									
									
									
										generated
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										146
									
								
								mirzaev/site/account/system/controllers/account_controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,146 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\controllers\core; | ||||||
|  | use mirzaev\site\account\models\account_model as account; | ||||||
|  | use mirzaev\site\account\models\session_model as session; | ||||||
|  | use mirzaev\site\account\models\vk_model as vk; | ||||||
|  |  | ||||||
|  | // Библиотека для ArangoDB | ||||||
|  | use ArangoDBClient\Document as _document; | ||||||
|  | use stdClass; | ||||||
|  |  | ||||||
|  | // Фреймворк для ВКонтакте | ||||||
|  | use mirzaev\vk\core as api; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Контроллер аккаунтов | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class account_controller extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Страница профиля | ||||||
|  |      * | ||||||
|  |      * @param array $parameters Параметры запроса | ||||||
|  |      */ | ||||||
|  |     public function index(array $parameters = []): ?string | ||||||
|  |     { | ||||||
|  |         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'] = account::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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										100
									
								
								mirzaev/site/account/system/controllers/core.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,100 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\views\manager; | ||||||
|  | use mirzaev\site\account\models\core as models; | ||||||
|  | use mirzaev\site\account\models\account_model as account; | ||||||
|  | use mirzaev\site\account\models\session_model as session; | ||||||
|  |  | ||||||
|  | // Библиотека для ArangoDB | ||||||
|  | use ArangoDBClient\Document as _document; | ||||||
|  |  | ||||||
|  | // Фреймворк PHP | ||||||
|  | use mirzaev\minimal\controller; | ||||||
|  |  | ||||||
|  | // Фреймворк ВКонтакте | ||||||
|  | use mirzaev\vk\core as vk; | ||||||
|  | use mirzaev\vk\robots\user as robot; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Ядро контроллеров | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | class core extends controller | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Переменные окружения | ||||||
|  |      */ | ||||||
|  |     protected robot $vk; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Переменные окружения | ||||||
|  |      */ | ||||||
|  |     protected array $variables = []; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Конструктор | ||||||
|  |      * | ||||||
|  |      * @return void | ||||||
|  |      */ | ||||||
|  |     public function __construct() | ||||||
|  |     { | ||||||
|  |         parent::__construct(); | ||||||
|  |  | ||||||
|  |         // Инициализация ядра моделей (соединение с базой данных...) | ||||||
|  |         new models(); | ||||||
|  |  | ||||||
|  |         // Инициализация журнала ошибок | ||||||
|  |         $this->variables['errors'] = [ | ||||||
|  |             'session' => [], | ||||||
|  |             'account' => [], | ||||||
|  |             'vk' => [] | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация даты до которой будет активна сессия | ||||||
|  |         $expires = time() + 604800; | ||||||
|  |  | ||||||
|  |         // Инициализация сессии (без журналирования) | ||||||
|  |         $this->variables['session'] = session::initialization($_COOKIE["session"] ?? null, $expires) ?? header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию'); | ||||||
|  |  | ||||||
|  |         if ($_COOKIE["session"] ?? null !== $this->variables['session']->hash) { | ||||||
|  |             // Изменился хеш сессии (подразумевается, что сессия устарела) | ||||||
|  |  | ||||||
|  |             // Запись хеша новой сессии | ||||||
|  |             setcookie('session', $this->variables['session']->hash, [ | ||||||
|  |                 'expires' => $expires, | ||||||
|  |                 'domain' => 'mirzaev.sexy', | ||||||
|  |                 'path' => '/', | ||||||
|  |                 'secure' => true, | ||||||
|  |                 'httponly' => true, | ||||||
|  |                 'samesite' => 'strict' | ||||||
|  |             ]); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Инициализация аккаунта (без журналирования) | ||||||
|  |         $this->variables['account'] = session::account($this->variables['session']); | ||||||
|  |  | ||||||
|  |         if ($this->variables['account'] instanceof _document) { | ||||||
|  |             // Инициализирован аккаунт | ||||||
|  |  | ||||||
|  |             // Инициализация аккаунта ВКонтакте (без журналирования) | ||||||
|  |             $this->variables['vk'] = account::vk($this->variables['account']); | ||||||
|  |  | ||||||
|  |             if ($this->variables['vk'] instanceof _document) { | ||||||
|  |                 // Инициализирован аккаунт ВКонтакте | ||||||
|  |  | ||||||
|  |                 // Инициализация робота для аккаунта ВКонтакте | ||||||
|  |                 $this->vk = vk::init()->user(key: $this->variables['vk']->access['key']); | ||||||
|  |             } else unset($this->variables['account'], $this->variables['vk']); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Инициализация препроцессора представления | ||||||
|  |         $this->view = new manager; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										44
									
								
								mirzaev/site/account/system/controllers/error_controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,44 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\controllers\core; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Контроллер ошибок | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class error_controller extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Страница с ошибкой | ||||||
|  |      * | ||||||
|  |      * @param array $parameters | ||||||
|  |      */ | ||||||
|  |     public function index(array $parameters = []): ?string | ||||||
|  |     { | ||||||
|  |         // Запись текста ошибки в переменную окружения | ||||||
|  |         $this->variables['text'] = $parameters['text'] ?? null; | ||||||
|  |  | ||||||
|  |         if (isset($parameters['code'])) { | ||||||
|  |             // Получен код ошибки | ||||||
|  |  | ||||||
|  |             // Запись кода ошибки в переменную окружения | ||||||
|  |             $this->variables['code'] = $parameters['code']; | ||||||
|  |  | ||||||
|  |             // Запись кода ответа | ||||||
|  |             http_response_code($parameters['code']); | ||||||
|  |  | ||||||
|  |             // Генерация представления | ||||||
|  |             return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . 'index.html', $this->variables); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Генерация представления | ||||||
|  |         return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . ($parameters['code'] ?? 'index') . '.html', $this->variables); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										59
									
								
								mirzaev/site/account/system/controllers/graph_controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,59 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\mirzaev\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\mirzaev\controllers\core; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Контроллер графика | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\mirzaev\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class graph_controller extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Страница с графиком | ||||||
|  |      * | ||||||
|  |      * Можно использовать совместно с элементом <iframe> для изоляции | ||||||
|  |      * содержимого бегущей строки от поисковых роботов | ||||||
|  |      * | ||||||
|  |      * @param array $parameters | ||||||
|  |      */ | ||||||
|  |     public function index(array $parameters = []): ?string | ||||||
|  |     { | ||||||
|  |         // Инициализация элементов для генерации в головном элементе | ||||||
|  |         $this->variables['head'] = [ | ||||||
|  |             'title' => 'Бегущая строка', | ||||||
|  |             'metas' => [ | ||||||
|  |                 [ | ||||||
|  |                     'attributes' => [ | ||||||
|  |                         'name' => 'robots', | ||||||
|  |                         'content' => 'nofollow' | ||||||
|  |                     ] | ||||||
|  |                 ] | ||||||
|  |             ] | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация бегущей строки | ||||||
|  |         $this->variables['graph'] = [ | ||||||
|  |             'id' => $this->variables['request']['id'] ?? 'graph' | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация аттрибутов бегущей строки | ||||||
|  |         $this->variables['graph']['attributes'] = [ | ||||||
|  |  | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация элементов бегущей строки | ||||||
|  |         $this->variables['graph']['elements'] = [ | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Генерация представления | ||||||
|  |         return $this->view->render(DIRECTORY_SEPARATOR . 'graph' . DIRECTORY_SEPARATOR . 'index.html', $this->variables); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -0,0 +1,82 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\controllers\core; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Контроллер бегущей строки | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class hotline_controller extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Страница с бегущей строкой | ||||||
|  |      * | ||||||
|  |      * Можно использовать совместно с элементом <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']['parameters'] = [ | ||||||
|  |             // 'step' => 2 | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация аттрибутов бегущей строки | ||||||
|  |         $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); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
							
								
								
									
										85
									
								
								mirzaev/site/account/system/controllers/index_controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,85 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\controllers; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\controllers\core; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Контроллер основной страницы | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class index_controller extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Главная страница | ||||||
|  |      * | ||||||
|  |      * @param array $parameters Параметры запроса | ||||||
|  |      */ | ||||||
|  |     public function index(array $parameters = []): ?string | ||||||
|  |     { | ||||||
|  |         // Инициализация загружаемых категорий | ||||||
|  |         $this->variables['include'] = [ | ||||||
|  |             'head' => ['self'], | ||||||
|  |             'body' => ['self'] | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация бегущей строки | ||||||
|  |         $this->variables['hotline'] = [ | ||||||
|  |             'id' => $this->variables['request']['id'] ?? 'hotline' | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация параметров бегущей строки | ||||||
|  |         $this->variables['hotline']['parameters'] = [ | ||||||
|  |             // 'step' => 2 | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация аттрибутов бегущей строки | ||||||
|  |         $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'] | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация бегущей строки | ||||||
|  |         $this->variables['graph'] = [ | ||||||
|  |             'id' => $this->variables['request']['id'] ?? 'graph' | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация аттрибутов бегущей строки | ||||||
|  |         $this->variables['graph']['attributes'] = [ | ||||||
|  |  | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Инициализация элементов бегущей строки | ||||||
|  |         $this->variables['graph']['elements'] = [ | ||||||
|  |         ]; | ||||||
|  |  | ||||||
|  |         // Генерация представления | ||||||
|  |         return $this->view->render(DIRECTORY_SEPARATOR . 'index.html', $this->variables); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										169
									
								
								mirzaev/site/account/system/models/account_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,169 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\models; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\models\vk_model as vk; | ||||||
|  |  | ||||||
|  | // Фреймворк ArangoDB | ||||||
|  | use mirzaev\arangodb\collection, | ||||||
|  |     mirzaev\arangodb\document; | ||||||
|  |  | ||||||
|  | // Библиотека для ArangoDB | ||||||
|  | use ArangoDBClient\Document as _document; | ||||||
|  |  | ||||||
|  | // Встроенные библиотеки | ||||||
|  | use exception; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Модель регистрации, аутентификации и авторизации | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\models | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class account_model extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Коллекция | ||||||
|  |      */ | ||||||
|  |     public const COLLECTION = 'account'; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Создать | ||||||
|  |      * | ||||||
|  |      * @param array &$errors Журнал ошибок | ||||||
|  |      * | ||||||
|  |      * @return ?_document Инстанция аккаунта, если удалось создать | ||||||
|  |      */ | ||||||
|  |     public static function create(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 | ||||||
|  |                 ))) { | ||||||
|  |                     // Найден созданный аккаунт | ||||||
|  |  | ||||||
|  |                     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 _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(), | ||||||
|  |                 'line' => $e->getLine(), | ||||||
|  |                 'stack' => $e->getTrace() | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Поиск связанного аккаунта ВКонтакте | ||||||
|  |      * | ||||||
|  |      * @param _document $account Инстанция аккаунта | ||||||
|  |      * @param array &$errors Журнал ошибок | ||||||
|  |      * | ||||||
|  |      * @return ?_document Инстанция аккаунта, если удалось найти | ||||||
|  |      */ | ||||||
|  |     public static function vk(_document $account, array &$errors = []): ?_document | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ( | ||||||
|  |                 collection::init(static::$db->session, self::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, vk::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true) | ||||||
|  |             ) { | ||||||
|  |                 // Инициализирована коллекция | ||||||
|  |  | ||||||
|  |                 if ($vk = collection::search(static::$db->session, sprintf( | ||||||
|  |                     <<<AQL | ||||||
|  |                         FOR document IN %s | ||||||
|  |                         LET edge = ( | ||||||
|  |                             FOR edge IN %s | ||||||
|  |                             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() | ||||||
|  |                 ))) { | ||||||
|  |                     // Найден аккаунт ВКонтакте | ||||||
|  |  | ||||||
|  |                     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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										143
									
								
								mirzaev/site/account/system/models/core.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,143 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\models; | ||||||
|  |  | ||||||
|  | use mirzaev\minimal\model; | ||||||
|  |  | ||||||
|  | use mirzaev\arangodb\connection; | ||||||
|  |  | ||||||
|  | use exception; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Ядро моделей | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\models | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | class core extends model | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Коллекция в которой хранятся аккаунты | ||||||
|  |      */ | ||||||
|  |     public const SETTINGS = '../settings/arangodb.php'; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Соединение с базой данных | ||||||
|  |      */ | ||||||
|  |     protected static connection $db; | ||||||
|  |  | ||||||
|  |     public function __construct(connection $db = null) | ||||||
|  |     { | ||||||
|  |         if (isset($db)) { | ||||||
|  |             // Получена инстанция соединения с базой данных | ||||||
|  |  | ||||||
|  |             // Запись и инициализация соединения с базой данных | ||||||
|  |             $this->__set('db', $db); | ||||||
|  |         } else { | ||||||
|  |             // Не получена инстанция соединения с базой данных | ||||||
|  |  | ||||||
|  |             // Инициализация соединения с базой данных по умолчанию | ||||||
|  |             $this->__get('db'); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Записать свойство | ||||||
|  |      * | ||||||
|  |      * @param string $name Название | ||||||
|  |      * @param mixed $value Значение | ||||||
|  |      */ | ||||||
|  |     public function __set(string $name, mixed $value = null): void | ||||||
|  |     { | ||||||
|  |         match ($name) { | ||||||
|  |             'db' => (function () use ($value) { | ||||||
|  |                 if ($this->__isset('db')) { | ||||||
|  |                     // Свойство уже было инициализировано | ||||||
|  |  | ||||||
|  |                     // Выброс исключения (неудача) | ||||||
|  |                     throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500); | ||||||
|  |                 } else { | ||||||
|  |                     // Свойство ещё не было инициализировано | ||||||
|  |  | ||||||
|  |                     if ($value instanceof connection) { | ||||||
|  |                         // Передано подходящее значение | ||||||
|  |  | ||||||
|  |                         // Запись свойства (успех) | ||||||
|  |                         self::$db = $value; | ||||||
|  |                     } else { | ||||||
|  |                         // Передано неподходящее значение | ||||||
|  |  | ||||||
|  |                         // Выброс исключения (неудача) | ||||||
|  |                         throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией mirzaev\arangodb\connection', 500); | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             })(), | ||||||
|  |             default => parent::__set($name, $value) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Прочитать свойство | ||||||
|  |      * | ||||||
|  |      * @param string $name Название | ||||||
|  |      * | ||||||
|  |      * @return mixed Содержимое | ||||||
|  |      */ | ||||||
|  |     public function __get(string $name): mixed | ||||||
|  |     { | ||||||
|  |         return match ($name) { | ||||||
|  |             'db' => (function () { | ||||||
|  |                 if (!$this->__isset('db')) { | ||||||
|  |                     // Свойство не инициализировано | ||||||
|  |  | ||||||
|  |                     // Инициализация значения по умолчанию исходя из настроек | ||||||
|  |                     $this->__set('db', new connection(require static::SETTINGS)); | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 return self::$db; | ||||||
|  |             })(), | ||||||
|  |             default => parent::__get($name) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Проверить свойство на инициализированность | ||||||
|  |      * | ||||||
|  |      * @param string $name Название | ||||||
|  |      */ | ||||||
|  |     public function __isset(string $name): bool | ||||||
|  |     { | ||||||
|  |         return match ($name) { | ||||||
|  |             default => parent::__isset($name) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Удалить свойство | ||||||
|  |      * | ||||||
|  |      * @param string $name Название | ||||||
|  |      */ | ||||||
|  |     public function __unset(string $name): void | ||||||
|  |     { | ||||||
|  |         match ($name) { | ||||||
|  |             default => parent::__isset($name) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Статический вызов | ||||||
|  |      * | ||||||
|  |      * @param string $name Название | ||||||
|  |      * @param array $arguments Параметры | ||||||
|  |      */ | ||||||
|  |     public static function __callStatic(string $name, array $arguments): mixed | ||||||
|  |     { | ||||||
|  |         match ($name) { | ||||||
|  |             'db' => (new static)->__get('db'), | ||||||
|  |             default => throw new exception("Не найдено свойство или функция: $name", 500) | ||||||
|  |         }; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										213
									
								
								mirzaev/site/account/system/models/session_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,213 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\models; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\models\account_model as account; | ||||||
|  |  | ||||||
|  | // Фреймворк ArangoDB | ||||||
|  | use mirzaev\arangodb\collection, | ||||||
|  |     mirzaev\arangodb\document; | ||||||
|  |  | ||||||
|  | // Библиотека для ArangoDB | ||||||
|  | use ArangoDBClient\Document as _document; | ||||||
|  |  | ||||||
|  | // Встроенные библиотеки | ||||||
|  | use exception; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Модель сессий | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\models | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class session_model extends core | ||||||
|  | { | ||||||
|  |     /** | ||||||
|  |      * Коллекция | ||||||
|  |      */ | ||||||
|  |     public const COLLECTION = 'session'; | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Инициализация | ||||||
|  |      * | ||||||
|  |      * @param ?string $hash Хеш сессии в базе данных | ||||||
|  |      * @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии) | ||||||
|  |      * @param array &$errors Журнал ошибок | ||||||
|  |      * | ||||||
|  |      * @return ?_document Инстанция сессии, если удалось найти или создать | ||||||
|  |      */ | ||||||
|  |     public static function initialization(?string $hash = null, ?int $expires = null, array &$errors = []): ?_document | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if (collection::init(static::$db->session, self::COLLECTION)) { | ||||||
|  |                 // Инициализирована коллекция | ||||||
|  |  | ||||||
|  |                 if (isset($hash) && $session = collection::search(static::$db->session, sprintf( | ||||||
|  |                     <<<AQL | ||||||
|  |                         FOR d IN %s | ||||||
|  |                         FILTER d.hash == '$hash' && d.expires > %d | ||||||
|  |                         RETURN d | ||||||
|  |                     AQL, | ||||||
|  |                     self::COLLECTION, | ||||||
|  |                     time() | ||||||
|  |                 ))) { | ||||||
|  |                     // Найдена сессия по хешу | ||||||
|  |  | ||||||
|  |                     // Возврат сессии | ||||||
|  |                     return $session; | ||||||
|  |                 } else if ($session = collection::search(static::$db->session, sprintf( | ||||||
|  |                     <<<AQL | ||||||
|  |                         FOR d IN %s | ||||||
|  |                         FILTER d.ip == '%s' && d.expires > %d | ||||||
|  |                         RETURN d | ||||||
|  |                     AQL, | ||||||
|  |                     self::COLLECTION, | ||||||
|  |                     $_SERVER['REMOTE_ADDR'], | ||||||
|  |                     time() | ||||||
|  |                 ))) { | ||||||
|  |                     // Найдена сессия по данным пользователя | ||||||
|  |  | ||||||
|  |                     // Возврат сессии | ||||||
|  |                     return $session; | ||||||
|  |                 } else { | ||||||
|  |                     // Не найдена сессия | ||||||
|  |  | ||||||
|  |                     // Запись сессии в базу данных | ||||||
|  |                     $_id = document::write(static::$db->session, self::COLLECTION, [ | ||||||
|  |                         'ip' => $_SERVER['REMOTE_ADDR'], | ||||||
|  |                         'expires' => $expires ?? time() + 604800 | ||||||
|  |                     ]); | ||||||
|  |  | ||||||
|  |                     if ($session = collection::search(static::$db->session, sprintf( | ||||||
|  |                         <<<AQL | ||||||
|  |                             FOR d IN %s | ||||||
|  |                             FILTER d._id == '$_id' && d.expires > %d | ||||||
|  |                             RETURN d | ||||||
|  |                         AQL, | ||||||
|  |                         self::COLLECTION, | ||||||
|  |                         time() | ||||||
|  |                     ))) { | ||||||
|  |                         // Найдена созданная сессия | ||||||
|  |  | ||||||
|  |                         // Запись хеша | ||||||
|  |                         $session->hash = sodium_bin2hex(sodium_crypto_generichash($_id)); | ||||||
|  |  | ||||||
|  |                         if (document::update(static::$db->session, $session)) { | ||||||
|  |                             // Записано обновление | ||||||
|  |  | ||||||
|  |                             return $session; | ||||||
|  |                         } 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() | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Связь сессии с аккаунтом | ||||||
|  |      * | ||||||
|  |      * @param _document $session Инстанция сессии | ||||||
|  |      * @param _document $account Инстанция аккаунта | ||||||
|  |      * @param array &$errors Журнал ошибок | ||||||
|  |      * | ||||||
|  |      * @return bool Статус выполнения | ||||||
|  |      */ | ||||||
|  |     public static function connect(_document $session, _document $account, array &$errors = []): bool | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ( | ||||||
|  |                 collection::init(static::$db->session, self::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, account::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true) | ||||||
|  |             ) { | ||||||
|  |                 // Инициализирована коллекция | ||||||
|  |  | ||||||
|  |                 if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [ | ||||||
|  |                     '_from' => $session->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(), | ||||||
|  |                 'line' => $e->getLine(), | ||||||
|  |                 'stack' => $e->getTrace() | ||||||
|  |             ]; | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         return false; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     /** | ||||||
|  |      * Поиск связанного аккаунта | ||||||
|  |      * | ||||||
|  |      * @param _document $session Инстанция сессии | ||||||
|  |      * @param array &$errors Журнал ошибок | ||||||
|  |      * | ||||||
|  |      * @return ?_document Инстанция аккаунта, если удалось найти | ||||||
|  |      */ | ||||||
|  |     public static function account(_document $session, array &$errors = []): ?_document | ||||||
|  |     { | ||||||
|  |         try { | ||||||
|  |             if ( | ||||||
|  |                 collection::init(static::$db->session, self::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, account::COLLECTION) | ||||||
|  |                 && collection::init(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, true) | ||||||
|  |             ) { | ||||||
|  |                 // Инициализированы коллекции | ||||||
|  |  | ||||||
|  |                 if ($account = 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, | ||||||
|  |                     account::COLLECTION, | ||||||
|  |                     self::COLLECTION . '_edge_' . account::COLLECTION, | ||||||
|  |                     $session->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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										555
									
								
								mirzaev/site/account/system/models/vk_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,555 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\models; | ||||||
|  |  | ||||||
|  | // Файлы проекта | ||||||
|  | use mirzaev\site\account\models\account_model as 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_model 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; | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								mirzaev/site/account/system/public/css/account.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | section#account { | ||||||
|  |     z-index : 10000; | ||||||
|  |     right   : 40px; | ||||||
|  |     bottom  : 40px; | ||||||
|  |     position: absolute; | ||||||
|  |     display : flex; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section#account>button#login { | ||||||
|  |     width           : 80px; | ||||||
|  |     height          : 80px; | ||||||
|  |     display         : flex; | ||||||
|  |     border-radius   : 100%; | ||||||
|  |     cursor          : pointer; | ||||||
|  |     overflow        : hidden; | ||||||
|  |     background-color: var(--background-inverse); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section#account>button#login>i.icon.authentication { | ||||||
|  |     width           : 12px; | ||||||
|  |     margin          : auto; | ||||||
|  |     color           : var(--text-inverse); | ||||||
|  |     background-color: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section#account>button#login:hover>i.icon.authentication { | ||||||
|  |     background-color: var(--grey-dark); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | section#account>button#login:active>i.icon.authentication { | ||||||
|  |     background-color: unset; | ||||||
|  | } | ||||||
							
								
								
									
										52
									
								
								mirzaev/site/account/system/public/css/books.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,52 @@ | |||||||
|  | main>section#books { | ||||||
|  |     display: flex; | ||||||
|  |     flex-flow: row wrap; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>* { | ||||||
|  |     margin-bottom: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>form.upload { | ||||||
|  |     width: calc(100% / 3 - 20px - 9px * 2); | ||||||
|  |     height: calc(220px - 9px * 2); | ||||||
|  |     margin: 5px; | ||||||
|  |     margin-right: 20px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>form.upload>p { | ||||||
|  |     font-size: 3rem; | ||||||
|  |     height: 0.3rem; | ||||||
|  |     line-height: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>article.book { | ||||||
|  |     width: calc(100% / 3 - 20px); | ||||||
|  |     margin-right: 20px; | ||||||
|  |     display: flex; | ||||||
|  |     flex-direction: column; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>article.book:nth-child(3) { | ||||||
|  |     width: calc(100% / 3); | ||||||
|  |     margin-right: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>article.book>img { | ||||||
|  |     height: 220px; | ||||||
|  |     object-fit: cover; | ||||||
|  |     object-position: right; | ||||||
|  |     overflow: hidden; | ||||||
|  |     clip-path: polygon(5px calc(100% - 5px), calc(100% - 5px) calc(100% - 5px), calc(100% - 5px) 5px, 5px 5px); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>article.book>h4 { | ||||||
|  |     margin-top: 5px; | ||||||
|  |     margin-bottom: 10px; | ||||||
|  |     height: 50px; | ||||||
|  |     text-align: center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main>section#books>article.book>p { | ||||||
|  |     margin: unset; | ||||||
|  | } | ||||||
							
								
								
									
										244
									
								
								mirzaev/site/account/system/public/css/graph.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,244 @@ | |||||||
|  | @keyframes node-select { | ||||||
|  |     from { | ||||||
|  |         outline-offset: 0px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     to { | ||||||
|  |         outline       : 2px solid var(--grey-light); | ||||||
|  |         outline-offset: 10px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes node-select-revert { | ||||||
|  |     from { | ||||||
|  |         outline       : 2px solid var(--grey-light); | ||||||
|  |         outline-offset: 10px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     to { | ||||||
|  |         outline-offset: 0px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes description-select { | ||||||
|  |     from { | ||||||
|  |         outline-offset: 0px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     to { | ||||||
|  |         outline       : 2px solid var(--grey); | ||||||
|  |         outline-offset: 10px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @keyframes description-select-revert { | ||||||
|  |     from { | ||||||
|  |         outline       : 2px solid var(--grey); | ||||||
|  |         outline-offset: 10px; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     to { | ||||||
|  |         outline-offset: 0px; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph { | ||||||
|  |     width     : 100vw; | ||||||
|  |     height    : 100vh; | ||||||
|  |     position  : absolute; | ||||||
|  |     transition: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph * { | ||||||
|  |     transition: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph:active { | ||||||
|  |     cursor: move; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node { | ||||||
|  |     z-index                  : 500; | ||||||
|  |     position                 : absolute; | ||||||
|  |     display                  : flex; | ||||||
|  |     cursor                   : grab; | ||||||
|  |     border-radius            : 100%; | ||||||
|  |     background-color         : var(--node-background); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.animated { | ||||||
|  |     animation-duration       : 0.1s; | ||||||
|  |     animation-name           : node-select-revert; | ||||||
|  |     animation-fill-mode      : forwards; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.animated:hover { | ||||||
|  |     animation-duration       : 0.1s; | ||||||
|  |     animation-name           : node-select; | ||||||
|  |     animation-fill-mode      : forwards; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node:active { | ||||||
|  |     cursor          : grabbing; | ||||||
|  |     background-color: var(--node-background-important); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>h4.title { | ||||||
|  |     margin    : auto; | ||||||
|  |     text-align: center; | ||||||
|  |     cursor    : pointer; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description { | ||||||
|  |     z-index         : 1000; | ||||||
|  |     position        : absolute; | ||||||
|  |     text-align      : justify; | ||||||
|  |     text-align-last : center; | ||||||
|  |     border-radius   : 100%; | ||||||
|  |     overflow        : hidden; | ||||||
|  |     background-color: var(--node-background-completed); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | /* section.graph article.node>div.description.animated { | ||||||
|  |     animation-duration       : 0.1s; | ||||||
|  |     animation-name           : description-select-revert; | ||||||
|  |     animation-fill-mode      : forwards; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description.animated:hover { | ||||||
|  |     animation-duration       : 0.1s; | ||||||
|  |     animation-name           : description-select; | ||||||
|  |     animation-fill-mode      : forwards; | ||||||
|  | } */ | ||||||
|  |  | ||||||
|  | section.graph article.node * { | ||||||
|  |     transition: 0.1s ease-in; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>span.wrapper { | ||||||
|  |     width       : 50%; | ||||||
|  |     height      : 100%; | ||||||
|  |     shape-margin: 15px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>span.left.wrapper { | ||||||
|  |     float        : left; | ||||||
|  |     shape-outside: polygon(100% 0%, 0% 0%, 0% 100%, 100% 100%, 68% 98%, 38% 90%, 10% 72%, 0% 50%, 10% 28%, 20% 20%, 100% 20%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>span.right.wrapper { | ||||||
|  |     float        : right; | ||||||
|  |     shape-outside: polygon(0% 100%, 100% 100%, 100% 0%, 0% 0%, 0% 20%, 82% 20%, 90% 28%, 100% 50%, 90% 72%, 60% 90%, 32% 98%); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>p { | ||||||
|  |     z-index   : 1500; | ||||||
|  |     position  : relative; | ||||||
|  |     margin    : 0; | ||||||
|  |     opacity   : 0; | ||||||
|  |     word-break: break-all; | ||||||
|  |     color     : var(--text-inverse); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description:hover>p { | ||||||
|  |     opacity: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>a.source { | ||||||
|  |     z-index    : 2000; | ||||||
|  |     top        : calc(20% - 1.3em + (1em - 1.3ex)); | ||||||
|  |     left       : 0; | ||||||
|  |     position   : absolute; | ||||||
|  |     font-weight: bold; | ||||||
|  |     font-size  : 1.3em; | ||||||
|  |     opacity    : 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.white>div.description>a.source { | ||||||
|  |     color: var(--text-inverse); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.white>div.description>a.source:active { | ||||||
|  |     color: var(--text-inverse-active); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.white>div.description>a.source:hover { | ||||||
|  |     color: var(--text-inverse-hover); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.red>div.description>a.source { | ||||||
|  |     color: var(--text-red); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.red>div.description>a.source:active { | ||||||
|  |     color: var(--text-red-active); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node.red>div.description>a.source:hover { | ||||||
|  |     color: var(--text-red-hover); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>a.source.red:active { | ||||||
|  |     color: var(--text-red-active); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description:hover>a.source { | ||||||
|  |     opacity: 1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>a.source:visited ::after { | ||||||
|  |     content         : ''; | ||||||
|  |     width           : 100%; | ||||||
|  |     height          : 100%; | ||||||
|  |     background-color: var(--node-background-completed); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description>img.cover { | ||||||
|  |     left          : 0; | ||||||
|  |     top           : 0; | ||||||
|  |     position      : absolute; | ||||||
|  |     width         : 100%; | ||||||
|  |     height        : 100%; | ||||||
|  |     object-fit    : cover; | ||||||
|  |     pointer-events: none; | ||||||
|  |     filter        : unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>div.description:hover>img.cover { | ||||||
|  |     filter: blur(5px) brightness(0.5); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>i.close { | ||||||
|  |     z-index   : -2000; | ||||||
|  |     top       : -10%; | ||||||
|  |     right     : -10%; | ||||||
|  |     position  : absolute; | ||||||
|  |     scale     : 0; | ||||||
|  |     opacity   : 0; | ||||||
|  |     cursor    : pointer; | ||||||
|  |     color     : var(--text); | ||||||
|  |     transition: 0.2s ease-out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>i.close:hover { | ||||||
|  |     scale     : 1.4 !important; | ||||||
|  |     color     : var(--text-hover); | ||||||
|  |     transition: 0.1s ease-in; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph article.node>i.close:active { | ||||||
|  |     scale     : 1.2 !important; | ||||||
|  |     color     : var(--text-active); | ||||||
|  |     transition: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph svg.connection { | ||||||
|  |     z-index : -500; | ||||||
|  |     position: absolute; | ||||||
|  |     width   : 100%; | ||||||
|  |     height  : 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.graph svg.connection>line { | ||||||
|  |     stroke: var(--connection); | ||||||
|  | } | ||||||
							
								
								
									
										31
									
								
								mirzaev/site/account/system/public/css/hotline.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,31 @@ | |||||||
|  | section.hotline { | ||||||
|  |     display: inline-flex; | ||||||
|  |     height : 100%; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.hotline * { | ||||||
|  |     transition: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.hotline:last-child { | ||||||
|  |     margin-bottom: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.hotline>article { | ||||||
|  |     margin-right    : 18px; | ||||||
|  |     width           : 140px; | ||||||
|  |     height          : 190px; | ||||||
|  |     display         : flex; | ||||||
|  |     align-self      : flex-end; | ||||||
|  |     border-radius   : 3px; | ||||||
|  |     background-color: var(--background-light-1); | ||||||
|  |     box-shadow      : 0px -6px 6px rgba(0, 0, 0, 0.3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.hotline>article:last-child { | ||||||
|  |     margin-right: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | section.hotline>article>* { | ||||||
|  |     margin: auto; | ||||||
|  | } | ||||||
| @@ -0,0 +1,40 @@ | |||||||
|  | .icon.authentication { | ||||||
|  |     box-sizing                : border-box; | ||||||
|  |     position                  : relative; | ||||||
|  |     display                   : block; | ||||||
|  |     transform                 : scale(var(--ggs, 1)); | ||||||
|  |     width                     : 6px; | ||||||
|  |     height                    : 16px; | ||||||
|  |     border                    : 2px solid; | ||||||
|  |     border-left               : 0; | ||||||
|  |     border-top-right-radius   : 2px; | ||||||
|  |     border-bottom-right-radius: 2px; | ||||||
|  |     margin-right              : -10px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon.authentication::after, | ||||||
|  | .icon.authentication::before { | ||||||
|  |     content   : ""; | ||||||
|  |     display   : block; | ||||||
|  |     box-sizing: border-box; | ||||||
|  |     position  : absolute | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon.authentication::after { | ||||||
|  |     border-top  : 2px solid; | ||||||
|  |     border-right: 2px solid; | ||||||
|  |     transform   : rotate(45deg); | ||||||
|  |     width       : 8px; | ||||||
|  |     height      : 8px; | ||||||
|  |     left        : -8px; | ||||||
|  |     bottom      : 2px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon.authentication::before { | ||||||
|  |     border-radius: 3px; | ||||||
|  |     width        : 10px; | ||||||
|  |     height       : 2px; | ||||||
|  |     background   : currentColor; | ||||||
|  |     left         : -11px; | ||||||
|  |     bottom       : 5px | ||||||
|  | } | ||||||
							
								
								
									
										29
									
								
								mirzaev/site/account/system/public/css/icon_close.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,29 @@ | |||||||
|  | .icon.close { | ||||||
|  |     box-sizing   : border-box; | ||||||
|  |     position     : relative; | ||||||
|  |     display      : block; | ||||||
|  |     transform    : scale(var(--ggs, 1)); | ||||||
|  |     width        : 22px; | ||||||
|  |     height       : 22px; | ||||||
|  |     border       : 2px solid transparent; | ||||||
|  |     border-radius: 40px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon.close::after, | ||||||
|  | .icon.close::before { | ||||||
|  |     content      : ""; | ||||||
|  |     display      : block; | ||||||
|  |     box-sizing   : border-box; | ||||||
|  |     position     : absolute; | ||||||
|  |     width        : 16px; | ||||||
|  |     height       : 2px; | ||||||
|  |     background   : currentColor; | ||||||
|  |     transform    : rotate(45deg); | ||||||
|  |     border-radius: 5px; | ||||||
|  |     top          : 8px; | ||||||
|  |     left         : 1px | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .icon.close::after { | ||||||
|  |     transform: rotate(-45deg) | ||||||
|  | } | ||||||
							
								
								
									
										185
									
								
								mirzaev/site/account/system/public/css/main.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,185 @@ | |||||||
|  | @import url('/fonts/comissioner.ttf'); | ||||||
|  |  | ||||||
|  | @media (prefers-color-scheme: light) { | ||||||
|  |     :root { | ||||||
|  |         --background               : #eee; | ||||||
|  |         --background-inverse       : #221e1e; | ||||||
|  |         --background-inverse-dark  : #120f0f; | ||||||
|  |         --node-background-important: #c3eac3; | ||||||
|  |         --node-background-completed: #b0c0b0; | ||||||
|  |         --node-background          : #bdb; | ||||||
|  |         --connection               : #b2b7b2; | ||||||
|  |         --connection-completed     : #d1d1d1; | ||||||
|  |         --text                     : #131313; | ||||||
|  |         --text-hover               : #292929; | ||||||
|  |         --text-active              : #0c0c0c; | ||||||
|  |         --text-inverse             : #e6e6e6; | ||||||
|  |         --text-inverse-hover       : #fff; | ||||||
|  |         --text-inverse-active      : #d0d0d0; | ||||||
|  |         --text-red                 : #f8a2a2; | ||||||
|  |         --text-red-hover           : #ffbcbc; | ||||||
|  |         --text-red-active          : #e69191; | ||||||
|  |         --link                     : #3c76ff; | ||||||
|  |         --link-hover               : #6594ff; | ||||||
|  |         --link-active              : #3064dd; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @media (prefers-color-scheme: dark) { | ||||||
|  |     :root { | ||||||
|  |         --background-light-3: #403939; | ||||||
|  |         --background-light-2: #322d2d; | ||||||
|  |         --background-light-1: #2b2525; | ||||||
|  |         --background-light  : #252020; | ||||||
|  |         --background        : #221e1e; | ||||||
|  |         --block-background  : ; | ||||||
|  |         --node-background   : #221e1e; | ||||||
|  |         --text              : #e6e6e6; | ||||||
|  |         --text-hover        : #fff; | ||||||
|  |         --text-active       : #d0d0d0; | ||||||
|  |         --text-inverse      : 'dark'; | ||||||
|  |         --red-light-1       : #dc4343; | ||||||
|  |         --red-light         : #bf3737; | ||||||
|  |         --red               : #a43333; | ||||||
|  |         --red-dark          : #8d2a2a; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | :root { | ||||||
|  |     --grey-light: #c0c0c0; | ||||||
|  |     --grey      : #858585; | ||||||
|  |     --grey-dark : #565656; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | * { | ||||||
|  |     text-decoration: none; | ||||||
|  |     outline        : none; | ||||||
|  |     border         : none; | ||||||
|  |     color          : var(--text); | ||||||
|  |     font-family    : 'Commissioner', sans-serif; | ||||||
|  |     transition     : 0.1s ease-out; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | .unselectable { | ||||||
|  |     -webkit-touch-callout: none; | ||||||
|  |     -webkit-user-select  : none; | ||||||
|  |     -khtml-user-select   : none; | ||||||
|  |     -moz-user-select     : none; | ||||||
|  |     -ms-user-select      : none; | ||||||
|  |     user-select          : none; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | a { | ||||||
|  |     color: var(--link); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | a:hover { | ||||||
|  |     color: var(--link-hover); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | a:active { | ||||||
|  |     color: var(--link-active); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | body { | ||||||
|  |     height          : 100vh; | ||||||
|  |     margin          : 0; | ||||||
|  |     overflow        : hidden; | ||||||
|  |     background-color: var(--background); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | aside { | ||||||
|  |     z-index    : 500; | ||||||
|  |     grid-column: 1/ 4; | ||||||
|  |     grid-row   : 2; | ||||||
|  |     overflow   : hidden; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header { | ||||||
|  |     z-index       : 5000; | ||||||
|  |     position      : absolute; | ||||||
|  |     display       : flex; | ||||||
|  |     flex-direction: column; | ||||||
|  |     box-shadow    : 2px 0 5px rgba(0, 0, 0, 0.3); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu { | ||||||
|  |     margin          : unset; | ||||||
|  |     padding         : 20px; | ||||||
|  |     display         : flex; | ||||||
|  |     flex-direction  : column; | ||||||
|  |     flex-grow       : 1; | ||||||
|  |     background-color: var(--background-light-1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>#account>button#login { | ||||||
|  |     z-index: 1500; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu a { | ||||||
|  |     margin-bottom: 8px; | ||||||
|  |     display      : flex; | ||||||
|  |     align-items  : center; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu a:last-child { | ||||||
|  |     margin-bottom: unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu a svg { | ||||||
|  |     margin-right: 8px; | ||||||
|  |     height      : 1.2rem; | ||||||
|  |     position    : relative; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu a:hover svg { | ||||||
|  |     margin-left : -5px; | ||||||
|  |     margin-right: 13px; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>menu a svg path { | ||||||
|  |     fill: var(--text); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>section { | ||||||
|  |     background-color: var(--background-light-1); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header :is(button, a[type="button"]) { | ||||||
|  |     width           : 100%; | ||||||
|  |     height          : 40px; | ||||||
|  |     display         : flex; | ||||||
|  |     justify-content : center; | ||||||
|  |     align-items     : center; | ||||||
|  |     cursor          : pointer; | ||||||
|  |     background-color: var(--red); | ||||||
|  |     transition      : unset; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header button { | ||||||
|  |     font-weight   : bold; | ||||||
|  |     text-transform: uppercase; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header :is(button, a[type="button"]):hover { | ||||||
|  |     background-color: var(--red-light); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header :is(button, a[type="button"]):active { | ||||||
|  |     background-color: var(--red-dark); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | header>nav { | ||||||
|  |     margin-top    : auto; | ||||||
|  |     display       : flex; | ||||||
|  |     flex-direction: column; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | main { | ||||||
|  |     z-index: 1000; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | footer { | ||||||
|  |     z-index : 3000; | ||||||
|  |     position: absolute; | ||||||
|  | } | ||||||
							
								
								
									
										33
									
								
								mirzaev/site/account/system/public/css/upload.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | form.upload { | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100px; | ||||||
|  |     position: relative; | ||||||
|  |     display: flex; | ||||||
|  |     border: 4px dashed #e5ddd1; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form.upload:hover { | ||||||
|  |     background-color: #ccc6bd; | ||||||
|  |     border: 4px dashed #fff7ea; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form.upload>p { | ||||||
|  |     margin: auto; | ||||||
|  |     font-weight: bold; | ||||||
|  |     color: #eee6d9; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form.upload:hover>p { | ||||||
|  |     color: #fff7ea; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form.upload>input { | ||||||
|  |     width: 100%; | ||||||
|  |     height: 100%; | ||||||
|  |     position: absolute; | ||||||
|  |     opacity: 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | form.upload:hover>input { | ||||||
|  |     cursor: pointer; | ||||||
|  | } | ||||||
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/fonts/commissioner.ttf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/images/botnet.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg height="32" width="32" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h32v32H0z"/><path d="M30 17.349V12h-2v-2h2V2H2v8h2v2H2v8h2v2H2v8h15.349A8.97 8.97 0 0 0 23 32a9.002 9.002 0 0 0 9-9 8.968 8.968 0 0 0-2-5.651zM14.059 22H6v-2h8.522a8.932 8.932 0 0 0-.463 2zM26 12H6v-2h20v2zM4 6h4v2H4V6zm0 10h4v2H4v-2zm4 12H4v-2h4v2zm15 1.883A6.898 6.898 0 0 1 16.115 23 6.898 6.898 0 0 1 23 16.115 6.898 6.898 0 0 1 29.883 23 6.898 6.898 0 0 1 23 29.883z"/><path d="m19 25 2 2 2-2 2 2 2-2-2-2 2-2-2-2-2 2-2-2-2 2 2 2z"/></svg> | ||||||
| After Width: | Height: | Size: 552 B | 
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/images/keylogger.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"><path d="M0 6c0-1.1.9-2 2-2h16a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V6zm2 0v2h2V6H2zm1 3v2h2V9H3zm-1 3v2h2v-2H2zm3 0v2h10v-2H5zm11 0v2h2v-2h-2zM6 9v2h2V9H6zm3 0v2h2V9H9zm3 0v2h2V9h-2zm3 0v2h2V9h-2zM5 6v2h2V6H5zm3 0v2h2V6H8zm3 0v2h2V6h-2zm3 0v2h4V6h-4z"/></svg> | ||||||
| After Width: | Height: | Size: 328 B | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/logo.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/logo_compressed.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 5.5 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/logo_red.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 17 KiB | 
| After Width: | Height: | Size: 5.3 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/logo_red_white.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 18 KiB | 
| After Width: | Height: | Size: 5.6 KiB | 
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/images/minecraft.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg viewBox="0 0 256 256" xmlns="http://www.w3.org/2000/svg"><path fill="none" d="M0 0h256v256H0z"/><path d="M224 177.3V78.7a8.1 8.1 0 0 0-4.1-7l-88-49.5a7.8 7.8 0 0 0-7.8 0l-88 49.5a8.1 8.1 0 0 0-4.1 7v98.6a8.1 8.1 0 0 0 4.1 7l88 49.5a7.8 7.8 0 0 0 7.8 0l88-49.5a8.1 8.1 0 0 0 4.1-7Z" fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m222.9 74.6-94 53.4-95.8-53.4M128.9 128l-.9 106.8"/></svg> | ||||||
| After Width: | Height: | Size: 537 B | 
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/images/miner.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="m11.136 12.117-.596 2.415c.736.185 3.004.921 3.34-.441.35-1.421-2.009-1.789-2.744-1.974Zm.813-3.297-.54 2.191c.612.154 2.5.784 2.806-.455.318-1.293-1.654-1.581-2.266-1.736ZM12 2a10 10 0 1 0 10 10A10 10 0 0 0 12 2Zm4.358 8.575a1.743 1.743 0 0 1-1.385 1.611 1.933 1.933 0 0 1 .997 2.661c-.586 1.692-1.977 1.835-3.827 1.481l-.449 1.82-1.085-.274.443-1.795c-.28-.07-.568-.145-.864-.227l-.445 1.804-1.084-.273.45-1.824c-.254-.065-.511-.135-.774-.201l-1.412-.356.539-1.256s.8.215.788.199a.394.394 0 0 0 .498-.26l1.217-4.939a.583.583 0 0 0-.505-.638c.016-.011-.789-.198-.789-.198l.29-1.172 1.495.378-.001.006c.225.056.457.11.693.164l.444-1.802 1.085.274-.436 1.766c.291.068.584.135.87.207l.432-1.755 1.085.274-.445 1.802c1.37.477 2.372 1.193 2.175 2.523Z"/></svg> | ||||||
| After Width: | Height: | Size: 825 B | 
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/images/stealer.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 7.4 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/truth.jpg
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 397 KiB | 
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/images/what.png
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 295 KiB | 
							
								
								
									
										36
									
								
								mirzaev/site/account/system/public/index.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,36 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account; | ||||||
|  |  | ||||||
|  | use mirzaev\minimal\core; | ||||||
|  | use mirzaev\minimal\router; | ||||||
|  |  | ||||||
|  | ini_set('error_reporting', E_ALL); | ||||||
|  | ini_set('display_errors', 1); | ||||||
|  | ini_set('display_startup_errors', 1); | ||||||
|  |  | ||||||
|  | define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views')); | ||||||
|  | define('STORAGE', realpath('..' . DIRECTORY_SEPARATOR . 'storage')); | ||||||
|  | define('INDEX', __DIR__); | ||||||
|  |  | ||||||
|  | // Автозагрузка | ||||||
|  | require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..'  . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php'; | ||||||
|  |  | ||||||
|  | // Инициализация маршрутазитора | ||||||
|  | $router = new router; | ||||||
|  |  | ||||||
|  | // Запись маршрутов | ||||||
|  | $router->write('/', 'index', 'index'); | ||||||
|  | $router->write('/system/hotline', 'hotline', 'index'); | ||||||
|  | $router->write('/system/graph', 'graph', 'index'); | ||||||
|  | $router->write('/account/initialization', 'account', 'initialization', 'PUT'); | ||||||
|  | $router->write('/account/vk/connect', 'account', 'connect'); | ||||||
|  | $router->write('/account/panel', 'account', 'panel'); | ||||||
|  |  | ||||||
|  | // Инициализация ядра | ||||||
|  | $core = new core(namespace: __NAMESPACE__, router: $router); | ||||||
|  |  | ||||||
|  | // Обработка запроса | ||||||
|  | echo $core->start(); | ||||||
							
								
								
									
										26
									
								
								mirzaev/site/account/system/public/js/account.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,26 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | class account { | ||||||
|  |     static async initialization() { | ||||||
|  |         // Запрос | ||||||
|  |         return fetch('https://auth.mirzaev.sexy/account/initialization', { | ||||||
|  |             method: 'GET' | ||||||
|  |         }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static authentication() { | ||||||
|  |         // Инициализация аккаунта | ||||||
|  |         alert(1); | ||||||
|  |         this.initialization() | ||||||
|  |             .then( | ||||||
|  |                 (response) => { | ||||||
|  |                     alert(2); | ||||||
|  |                 } | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static deauthentication() { | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1332
									
								
								mirzaev/site/account/system/public/js/graph.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										668
									
								
								mirzaev/site/account/system/public/js/hotline.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,668 @@ | |||||||
|  | 'use strict'; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Бегущая строка | ||||||
|  |  * | ||||||
|  |  * @description | ||||||
|  |  * Простой, но мощный класс для создания бегущих строк. Поддерживает | ||||||
|  |  * перемещение мышью и прокрутку колесом, полностью настраивается очень гибок | ||||||
|  |  * для настроек в CSS и подразумевается, что отлично индексируется поисковыми роботами. | ||||||
|  |  * Имеет свой препроцессор, благодаря которому можно создавать бегущие строки | ||||||
|  |  * без программирования - с помощью HTML-аттрибутов, а так же возможность | ||||||
|  |  * изменять параметры (data-hotline-* аттрибуты) на лету. Есть возможность вызывать | ||||||
|  |  * события при выбранных действиях для того, чтобы пользователь имел возможность | ||||||
|  |  * дорабатывать функционал без изучения и изменения моего кода | ||||||
|  |  * | ||||||
|  |  * @example | ||||||
|  |  * сonst hotline = new hotline(); | ||||||
|  |  * hotline.step = '-5'; | ||||||
|  |  * hotline.start(); | ||||||
|  |  * | ||||||
|  |  * @todo | ||||||
|  |  * 1. Бесконечный режим - элементы не удаляются если видны на экране (будут дубликаты) | ||||||
|  |  * | ||||||
|  |  * @copyright WTFPL | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | class hotline { | ||||||
|  |   // Идентификатор | ||||||
|  |   #id = 0; | ||||||
|  |  | ||||||
|  |   // Оболочка (instanceof HTMLElement) | ||||||
|  |   #shell = document.getElementById("hotline"); | ||||||
|  |  | ||||||
|  |   // Инстанция горячей строки | ||||||
|  |   #instance = null; | ||||||
|  |  | ||||||
|  |   // Перемещение | ||||||
|  |   #transfer = true; | ||||||
|  |  | ||||||
|  |   // Движение | ||||||
|  |   #move = true; | ||||||
|  |  | ||||||
|  |   // Наблюдатель | ||||||
|  |   #observer = null; | ||||||
|  |  | ||||||
|  |   // Наблюдатель | ||||||
|  |   #block = new Set(["events"]); | ||||||
|  |  | ||||||
|  |   // Настраиваемые параметры | ||||||
|  |   transfer = null; | ||||||
|  |   move = null; | ||||||
|  |   delay = 10; | ||||||
|  |   step = 1; | ||||||
|  |   hover = true; | ||||||
|  |   movable = true; | ||||||
|  |   sticky = false; | ||||||
|  |   wheel = false; | ||||||
|  |   delta = null; | ||||||
|  |   vertical = false; | ||||||
|  |   observe = false; | ||||||
|  |   events = new Map([ | ||||||
|  |     ["start", false], | ||||||
|  |     ["stop", false], | ||||||
|  |     ["move", false], | ||||||
|  |     ["move.block", false], | ||||||
|  |     ["move.unblock", false], | ||||||
|  |     ["offset", false], | ||||||
|  |     ["transfer.start", true], | ||||||
|  |     ["transfer.end", true], | ||||||
|  |     ["onmousemove", false] | ||||||
|  |   ]); | ||||||
|  |  | ||||||
|  |   constructor(id, shell) { | ||||||
|  |     // Запись идентификатора | ||||||
|  |     if (typeof id === "string" || typeof id === "number") this.#id = id; | ||||||
|  |  | ||||||
|  |     // Запись оболочки | ||||||
|  |     if (shell instanceof HTMLElement) this.#shell = shell; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   start() { | ||||||
|  |     if (this.#instance === null) { | ||||||
|  |       // Нет запущенной инстанции бегущей строки | ||||||
|  |  | ||||||
|  |       // Инициализация ссылки на ядро | ||||||
|  |       const _this = this; | ||||||
|  |  | ||||||
|  |       // Запуск движения | ||||||
|  |       this.#instance = setInterval(function () { | ||||||
|  |         if (_this.#shell.childElementCount > 1) { | ||||||
|  |           // Найдено содержимое бегущей строки (2 и более) | ||||||
|  |  | ||||||
|  |           // Инициализация буфера для временных данных | ||||||
|  |           let buffer; | ||||||
|  |  | ||||||
|  |           // Инициализация данных первого элемента в строке | ||||||
|  |           const first = { | ||||||
|  |             element: (buffer = _this.#shell.firstElementChild), | ||||||
|  |             coords: buffer.getBoundingClientRect() | ||||||
|  |           }; | ||||||
|  |  | ||||||
|  |           if (_this.vertical) { | ||||||
|  |             // Вертикальная бегущая строка | ||||||
|  |  | ||||||
|  |             // Инициализация сдвига у первого элемента (движение) | ||||||
|  |             first.offset = isNaN( | ||||||
|  |               (buffer = parseFloat(first.element.style.marginTop)) | ||||||
|  |             ) | ||||||
|  |               ? 0 | ||||||
|  |               : buffer; | ||||||
|  |  | ||||||
|  |             // Инициализация отступа до второго элемента у первого элемента (разделение) | ||||||
|  |             first.separator = isNaN( | ||||||
|  |               (buffer = parseFloat( | ||||||
|  |                 getComputedStyle(first.element).marginBottom | ||||||
|  |               )) | ||||||
|  |             ) | ||||||
|  |               ? 0 | ||||||
|  |               : buffer; | ||||||
|  |  | ||||||
|  |             // Инициализация крайнего с конца ребра первого элемента в строке | ||||||
|  |             first.end = first.coords.y + first.coords.height + first.separator; | ||||||
|  |           } else { | ||||||
|  |             // Горизонтальная бегущая строка | ||||||
|  |  | ||||||
|  |             // Инициализация отступа у первого элемента (движение) | ||||||
|  |             first.offset = isNaN( | ||||||
|  |               (buffer = parseFloat(first.element.style.marginLeft)) | ||||||
|  |             ) | ||||||
|  |               ? 0 | ||||||
|  |               : buffer; | ||||||
|  |  | ||||||
|  |             // Инициализация отступа до второго элемента у первого элемента (разделение) | ||||||
|  |             first.separator = isNaN( | ||||||
|  |               (buffer = parseFloat(getComputedStyle(first.element).marginRight)) | ||||||
|  |             ) | ||||||
|  |               ? 0 | ||||||
|  |               : buffer; | ||||||
|  |  | ||||||
|  |             // Инициализация крайнего с конца ребра первого элемента в строке | ||||||
|  |             first.end = first.coords.x + first.coords.width + first.separator; | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           if ( | ||||||
|  |             (_this.vertical && | ||||||
|  |               Math.round(first.end) < _this.#shell.offsetTop) || | ||||||
|  |             (!_this.vertical && Math.round(first.end) < _this.#shell.offsetLeft) | ||||||
|  |           ) { | ||||||
|  |             // Элемент (вместе с отступом до второго элемента) вышел из области видимости (строки) | ||||||
|  |  | ||||||
|  |             if ( | ||||||
|  |               (_this.transfer === null && _this.#transfer) || | ||||||
|  |               _this.transfer === true | ||||||
|  |             ) { | ||||||
|  |               // Перенос разрешен | ||||||
|  |  | ||||||
|  |               if (_this.vertical) { | ||||||
|  |                 // Вертикальная бегущая строка | ||||||
|  |  | ||||||
|  |                 // Удаление отступов (движения) | ||||||
|  |                 first.element.style.marginTop = null; | ||||||
|  |               } else { | ||||||
|  |                 // Горизонтальная бегущая строка | ||||||
|  |  | ||||||
|  |                 // Удаление отступов (движения) | ||||||
|  |                 first.element.style.marginLeft = null; | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               // Копирование первого элемента в конец строки | ||||||
|  |               _this.#shell.appendChild(first.element); | ||||||
|  |  | ||||||
|  |               if (_this.events.get("transfer.end")) { | ||||||
|  |                 // Запрошен вызов события: "перемещение в конец" | ||||||
|  |  | ||||||
|  |                 // Вызов события: "перемещение в конец" | ||||||
|  |                 document.dispatchEvent( | ||||||
|  |                   new CustomEvent(`hotline.${_this.#id}.transfer.end`, { | ||||||
|  |                     detail: { | ||||||
|  |                       element: first.element, | ||||||
|  |                       offset: -( | ||||||
|  |                         (_this.vertical | ||||||
|  |                           ? first.coords.height | ||||||
|  |                           : first.coords.width) + first.separator | ||||||
|  |                       ) | ||||||
|  |                     } | ||||||
|  |                   }) | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } else if ( | ||||||
|  |             (_this.vertical && | ||||||
|  |               Math.round(first.coords.y) > _this.#shell.offsetTop) || | ||||||
|  |             (!_this.vertical && | ||||||
|  |               Math.round(first.coords.x) > _this.#shell.offsetLeft) | ||||||
|  |           ) { | ||||||
|  |             // Передняя (движущая) граница первого элемента вышла из области видимости | ||||||
|  |  | ||||||
|  |             if ( | ||||||
|  |               (_this.transfer === null && _this.#transfer) || | ||||||
|  |               _this.transfer === true | ||||||
|  |             ) { | ||||||
|  |               // Перенос разрешен | ||||||
|  |  | ||||||
|  |               // Инициализация отступа у последнего элемента (разделение) | ||||||
|  |               const separator = | ||||||
|  |                 (buffer = isNaN( | ||||||
|  |                   (buffer = parseFloat( | ||||||
|  |                     getComputedStyle(_this.#shell.lastElementChild)[ | ||||||
|  |                     _this.vertical ? "marginBottom" : "marginRight" | ||||||
|  |                     ] | ||||||
|  |                   )) | ||||||
|  |                 ) | ||||||
|  |                   ? 0 | ||||||
|  |                   : buffer) === 0 | ||||||
|  |                   ? first.separator | ||||||
|  |                   : buffer; | ||||||
|  |  | ||||||
|  |               // Инициализация координат первого элемента в строке | ||||||
|  |               const coords = _this.#shell.lastElementChild.getBoundingClientRect(); | ||||||
|  |  | ||||||
|  |               if (_this.vertical) { | ||||||
|  |                 // Вертикальная бегущая строка | ||||||
|  |  | ||||||
|  |                 // Удаление отступов (движения) | ||||||
|  |                 _this.#shell.lastElementChild.style.marginTop = | ||||||
|  |                   -coords.height - separator + "px"; | ||||||
|  |               } else { | ||||||
|  |                 // Горизонтальная бегущая строка | ||||||
|  |  | ||||||
|  |                 // Удаление отступов (движения) | ||||||
|  |                 _this.#shell.lastElementChild.style.marginLeft = | ||||||
|  |                   -coords.width - separator + "px"; | ||||||
|  |               } | ||||||
|  |  | ||||||
|  |               // Копирование последнего элемента в начало строки | ||||||
|  |               _this.#shell.insertBefore( | ||||||
|  |                 _this.#shell.lastElementChild, | ||||||
|  |                 first.element | ||||||
|  |               ); | ||||||
|  |  | ||||||
|  |               // Удаление отступов у второго элемента в строке (движения) | ||||||
|  |               _this.#shell.children[1].style[ | ||||||
|  |                 _this.vertical ? "marginTop" : "marginLeft" | ||||||
|  |               ] = null; | ||||||
|  |  | ||||||
|  |               if (_this.events.get("transfer.start")) { | ||||||
|  |                 // Запрошен вызов события: "перемещение в начало" | ||||||
|  |  | ||||||
|  |                 // Вызов события: "перемещение в начало" | ||||||
|  |                 document.dispatchEvent( | ||||||
|  |                   new CustomEvent(`hotline.${_this.#id}.transfer.start`, { | ||||||
|  |                     detail: { | ||||||
|  |                       element: _this.#shell.lastElementChild, | ||||||
|  |                       offset: | ||||||
|  |                         (_this.vertical ? coords.height : coords.width) + | ||||||
|  |                         separator | ||||||
|  |                     } | ||||||
|  |                   }) | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } else { | ||||||
|  |             // Элемент в области видимости | ||||||
|  |  | ||||||
|  |             if ((_this.move === null && _this.#move) || _this.move === true) { | ||||||
|  |               // Движение разрешено | ||||||
|  |  | ||||||
|  |               // Запись новых координат сдвига | ||||||
|  |               const offset = first.offset + _this.step; | ||||||
|  |  | ||||||
|  |               // Запись сдвига (движение) | ||||||
|  |               _this.offset(offset); | ||||||
|  |  | ||||||
|  |               if (_this.events.get("move")) { | ||||||
|  |                 // Запрошен вызов события: "движение" | ||||||
|  |  | ||||||
|  |                 // Вызов события: "движение" | ||||||
|  |                 document.dispatchEvent( | ||||||
|  |                   new CustomEvent(`hotline.${_this.#id}.move`, { | ||||||
|  |                     detail: { | ||||||
|  |                       from: first.offset, | ||||||
|  |                       to: offset | ||||||
|  |                     } | ||||||
|  |                   }) | ||||||
|  |                 ); | ||||||
|  |               } | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |         } | ||||||
|  |       }, _this.delay); | ||||||
|  |  | ||||||
|  |       if (this.hover) { | ||||||
|  |         // Запрошена возможность останавливать бегущую строку | ||||||
|  |  | ||||||
|  |         // Инициализация сдвига | ||||||
|  |         let offset = 0; | ||||||
|  |  | ||||||
|  |         // Инициализация слушателя события при перемещении элемента в бегущей строке | ||||||
|  |         const listener = function (e) { | ||||||
|  |           // Увеличение сдвига | ||||||
|  |           offset += e.detail.offset ?? 0; | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Инициализация обработчика наведения курсора (остановка движения) | ||||||
|  |         this.#shell.onmouseover = function (e) { | ||||||
|  |           // Курсор наведён на бегущую строку | ||||||
|  |  | ||||||
|  |           // Блокировка движения | ||||||
|  |           _this.#move = false; | ||||||
|  |  | ||||||
|  |           if (_this.events.get("move.block")) { | ||||||
|  |             // Запрошен вызов события: "блокировка движения" | ||||||
|  |  | ||||||
|  |             // Вызов события: "блокировка движения" | ||||||
|  |             document.dispatchEvent( | ||||||
|  |               new CustomEvent(`hotline.${_this.#id}.move.block`) | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           if (_this.movable) { | ||||||
|  |             // Запрошена возможность двигать бегущую строку | ||||||
|  |  | ||||||
|  |             _this.#shell.onmousedown = function (onmousedown) { | ||||||
|  |               // Курсор активирован | ||||||
|  |  | ||||||
|  |               // Инициализация слушателей события перемещения элемента в бегущей строке | ||||||
|  |               document.addEventListener( | ||||||
|  |                 `hotline.${_this.#id}.transfer.start`, | ||||||
|  |                 listener | ||||||
|  |               ); | ||||||
|  |               document.addEventListener( | ||||||
|  |                 `hotline.${_this.#id}.transfer.end`, | ||||||
|  |                 listener | ||||||
|  |               ); | ||||||
|  |  | ||||||
|  |               // Инициализация буфера для временных данных | ||||||
|  |               let buffer; | ||||||
|  |  | ||||||
|  |               // Инициализация данных первого элемента в строке | ||||||
|  |               const first = { | ||||||
|  |                 offset: isNaN( | ||||||
|  |                   (buffer = parseFloat( | ||||||
|  |                     _this.vertical | ||||||
|  |                       ? _this.#shell.firstElementChild.style.marginTop | ||||||
|  |                       : _this.#shell.firstElementChild.style.marginLeft | ||||||
|  |                   )) | ||||||
|  |                 ) | ||||||
|  |                   ? 0 | ||||||
|  |                   : buffer | ||||||
|  |               }; | ||||||
|  |  | ||||||
|  |               document.onmousemove = function (onmousemove) { | ||||||
|  |                 // Курсор движется | ||||||
|  |  | ||||||
|  |                 if (_this.vertical) { | ||||||
|  |                   // Вертикальная бегущая строка | ||||||
|  |  | ||||||
|  |                   // Инициализация буфера местоположения | ||||||
|  |                   const from = _this.#shell.firstElementChild.style.marginTop; | ||||||
|  |                   const to = onmousemove.pageY - (onmousedown.pageY + offset - first.offset); | ||||||
|  |  | ||||||
|  |                   // Движение | ||||||
|  |                   _this.#shell.firstElementChild.style.marginTop = to + | ||||||
|  |                     "px"; | ||||||
|  |  | ||||||
|  |                   if (_this.events.get("onmousemove")) { | ||||||
|  |                     // Запрошен вызов события: "перемещение мышью" | ||||||
|  |  | ||||||
|  |                     // Вызов события: "перемещение мышью" | ||||||
|  |                     document.dispatchEvent( | ||||||
|  |                       new CustomEvent(`hotline.${_this.#id}.onmousemove`, { | ||||||
|  |                         detail: { from, to } | ||||||
|  |                       }) | ||||||
|  |                     ); | ||||||
|  |                   } | ||||||
|  |                 } else { | ||||||
|  |                   // Горизонтальная бегущая строка | ||||||
|  |  | ||||||
|  |                   // Инициализация буфера местоположения | ||||||
|  |                   const from = _this.#shell.firstElementChild.style.marginLeft; | ||||||
|  |                   const to = onmousemove.pageX - (onmousedown.pageX + offset - first.offset); | ||||||
|  |  | ||||||
|  |                   // Движение | ||||||
|  |                   _this.#shell.firstElementChild.style.marginLeft = to + "px"; | ||||||
|  |  | ||||||
|  |                   if (_this.events.get("onmousemove")) { | ||||||
|  |                     // Запрошен вызов события: "перемещение мышью" | ||||||
|  |  | ||||||
|  |                     // Вызов события: "перемещение мышью" | ||||||
|  |                     document.dispatchEvent( | ||||||
|  |                       new CustomEvent(`hotline.${_this.#id}.onmousemove`, { | ||||||
|  |                         detail: { from, to } | ||||||
|  |                       }) | ||||||
|  |                     ); | ||||||
|  |                   } | ||||||
|  |                 } | ||||||
|  |  | ||||||
|  |                 // Запись курсора | ||||||
|  |                 _this.#shell.style.cursor = "grabbing"; | ||||||
|  |               }; | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             // Перещапись событий браузера (чтобы не дёргалось) | ||||||
|  |             _this.#shell.ondragstart = null; | ||||||
|  |  | ||||||
|  |             _this.#shell.onmouseup = function () { | ||||||
|  |               // Курсор деактивирован | ||||||
|  |  | ||||||
|  |               // Остановка обработки движения | ||||||
|  |               document.onmousemove = null; | ||||||
|  |  | ||||||
|  |               // Сброс сдвига | ||||||
|  |               offset = 0; | ||||||
|  |  | ||||||
|  |               document.removeEventListener( | ||||||
|  |                 `hotline.${_this.#id}.transfer.start`, | ||||||
|  |                 listener | ||||||
|  |               ); | ||||||
|  |               document.removeEventListener( | ||||||
|  |                 `hotline.${_this.#id}.transfer.end`, | ||||||
|  |                 listener | ||||||
|  |               ); | ||||||
|  |  | ||||||
|  |               // Восстановление курсора | ||||||
|  |               _this.#shell.style.cursor = null; | ||||||
|  |             }; | ||||||
|  |           } | ||||||
|  |         }; | ||||||
|  |  | ||||||
|  |         // Инициализация обработчика отведения курсора (остановка движения) | ||||||
|  |         this.#shell.onmouseleave = function (onmouseleave) { | ||||||
|  |           // Курсор отведён от бегущей строки | ||||||
|  |  | ||||||
|  |           if (!_this.sticky) { | ||||||
|  |             // Отключено прилипание | ||||||
|  |  | ||||||
|  |             // Остановка обработки движения | ||||||
|  |             document.onmousemove = null; | ||||||
|  |  | ||||||
|  |             document.removeEventListener( | ||||||
|  |               `hotline.${_this.#id}.transfer.start`, | ||||||
|  |               listener | ||||||
|  |             ); | ||||||
|  |             document.removeEventListener( | ||||||
|  |               `hotline.${_this.#id}.transfer.end`, | ||||||
|  |               listener | ||||||
|  |             ); | ||||||
|  |  | ||||||
|  |             // Восстановление курсора | ||||||
|  |             _this.#shell.style.cursor = null; | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           // Сброс сдвига | ||||||
|  |           offset = 0; | ||||||
|  |  | ||||||
|  |           // Разблокировка движения | ||||||
|  |           _this.#move = true; | ||||||
|  |  | ||||||
|  |           if (_this.events.get("move.unblock")) { | ||||||
|  |             // Запрошен вызов события: "разблокировка движения" | ||||||
|  |  | ||||||
|  |             // Вызов события: "разблокировка движения" | ||||||
|  |             document.dispatchEvent( | ||||||
|  |               new CustomEvent(`hotline.${_this.#id}.move.unblock`) | ||||||
|  |             ); | ||||||
|  |           } | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |  | ||||||
|  |       if (this.wheel) { | ||||||
|  |         // Запрошена возможность прокручивать колесом мыши | ||||||
|  |  | ||||||
|  |         // Инициализация обработчика наведения курсора (остановка движения) | ||||||
|  |         this.#shell.onwheel = function (e) { | ||||||
|  |           // Курсор наведён на бегущую | ||||||
|  |  | ||||||
|  |           // Инициализация буфера для временных данных | ||||||
|  |           let buffer; | ||||||
|  |  | ||||||
|  |           // Перемещение | ||||||
|  |           _this.offset( | ||||||
|  |             (isNaN( | ||||||
|  |               (buffer = parseFloat( | ||||||
|  |                 _this.#shell.firstElementChild.style[ | ||||||
|  |                 _this.vertical ? "marginTop" : "marginLeft" | ||||||
|  |                 ] | ||||||
|  |               )) | ||||||
|  |             ) | ||||||
|  |               ? 0 | ||||||
|  |               : buffer) + | ||||||
|  |             (_this.delta === null | ||||||
|  |               ? e.wheelDelta | ||||||
|  |               : e.wheelDelta > 0 | ||||||
|  |                 ? _this.delta | ||||||
|  |                 : -_this.delta) | ||||||
|  |           ); | ||||||
|  |         }; | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.observe) { | ||||||
|  |       // Запрошено наблюдение за изменениями аттрибутов элемента бегущей строки | ||||||
|  |  | ||||||
|  |       if (this.#observer === null) { | ||||||
|  |         // Отсутствует наблюдатель | ||||||
|  |  | ||||||
|  |         // Инициализация ссылки на ядро | ||||||
|  |         const _this = this; | ||||||
|  |  | ||||||
|  |         // Инициализация наблюдателя | ||||||
|  |         this.#observer = new MutationObserver(function (mutations) { | ||||||
|  |           for (const mutation of mutations) { | ||||||
|  |             if (mutation.type === "attributes") { | ||||||
|  |               // Запись параметра в инстанцию бегущей строки | ||||||
|  |               _this.write(mutation.attributeName); | ||||||
|  |             } | ||||||
|  |           } | ||||||
|  |  | ||||||
|  |           // Перезапуск бегущей строки | ||||||
|  |           _this.restart(); | ||||||
|  |         }); | ||||||
|  |  | ||||||
|  |         // Активация наблюдения | ||||||
|  |         this.#observer.observe(this.#shell, { | ||||||
|  |           attributes: true | ||||||
|  |         }); | ||||||
|  |       } | ||||||
|  |     } else if (this.#observer instanceof MutationObserver) { | ||||||
|  |       // Запрошено отключение наблюдения | ||||||
|  |  | ||||||
|  |       // Деактивация наблюдения | ||||||
|  |       this.#observer.disconnect(); | ||||||
|  |  | ||||||
|  |       // Удаление наблюдателя | ||||||
|  |       this.#observer = null; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (this.events.get("start")) { | ||||||
|  |       // Запрошен вызов события: "запуск" | ||||||
|  |  | ||||||
|  |       // Вызов события: "запуск" | ||||||
|  |       document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.start`)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   stop() { | ||||||
|  |     // Остановка бегущей строки | ||||||
|  |     clearInterval(this.#instance); | ||||||
|  |  | ||||||
|  |     // Удаление инстанции интервала | ||||||
|  |     this.#instance = null; | ||||||
|  |  | ||||||
|  |     if (this.events.get("stop")) { | ||||||
|  |       // Запрошен вызов события: "остановка" | ||||||
|  |  | ||||||
|  |       // Вызов события: "остановка" | ||||||
|  |       document.dispatchEvent(new CustomEvent(`hotline.${this.#id}.stop`)); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   restart() { | ||||||
|  |     // Остановка бегущей строки | ||||||
|  |     this.stop(); | ||||||
|  |  | ||||||
|  |     // Запуск бегущей строки | ||||||
|  |     this.start(); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   write(attribute) { | ||||||
|  |     // Инициализация названия параметра | ||||||
|  |     const parameter = (/^data-hotline-(\w+)$/.exec(attribute) ?? [, null])[1]; | ||||||
|  |  | ||||||
|  |     if (typeof parameter === "string") { | ||||||
|  |       // Параметр найден | ||||||
|  |  | ||||||
|  |       // Проверка на разрешение изменения | ||||||
|  |       if (this.#block.has(parameter)) return; | ||||||
|  |  | ||||||
|  |       // Инициализация значения параметра | ||||||
|  |       const value = this.#shell.getAttribute(attribute); | ||||||
|  |  | ||||||
|  |       // Инициализация буфера для временных данных | ||||||
|  |       let buffer; | ||||||
|  |  | ||||||
|  |       // Запись параметра | ||||||
|  |       this[parameter] = isNaN((buffer = parseFloat(value))) | ||||||
|  |         ? value === "true" | ||||||
|  |           ? true | ||||||
|  |           : value === "false" | ||||||
|  |             ? false | ||||||
|  |             : value | ||||||
|  |         : buffer; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   offset(value) { | ||||||
|  |     // Запись отступа | ||||||
|  |     this.#shell.firstElementChild.style[ | ||||||
|  |       this.vertical ? "marginTop" : "marginLeft" | ||||||
|  |     ] = value + "px"; | ||||||
|  |  | ||||||
|  |     if (this.events.get("offset")) { | ||||||
|  |       // Запрошен вызов события: "сдвиг" | ||||||
|  |  | ||||||
|  |       // Вызов события: "сдвиг" | ||||||
|  |       document.dispatchEvent( | ||||||
|  |         new CustomEvent(`hotline.${this.#id}.offset`, { | ||||||
|  |           detail: { | ||||||
|  |             to: value | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static preprocessing(event = false) { | ||||||
|  |     // Инициализация счётчиков инстанций горячей строки | ||||||
|  |     const success = new Set(); | ||||||
|  |     let error = 0; | ||||||
|  |  | ||||||
|  |     for (const element of document.querySelectorAll('*[data-hotline="true"]')) { | ||||||
|  |       // Перебор бегущих строк | ||||||
|  |  | ||||||
|  |       if (typeof element.id === "string") { | ||||||
|  |         // Найден идентификатор | ||||||
|  |  | ||||||
|  |         // Инициализация инстанции бегущей строки | ||||||
|  |         const hotline = new this(element.id, element); | ||||||
|  |  | ||||||
|  |         for (const attribute of element.getAttributeNames()) { | ||||||
|  |           // Перебор аттрибутов | ||||||
|  |  | ||||||
|  |           // Запись параметра в инстанцию бегущей строки | ||||||
|  |           hotline.write(attribute); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         // Запуск бегущей строки | ||||||
|  |         hotline.start(); | ||||||
|  |  | ||||||
|  |         // Запись инстанции бегущей строки в элемент | ||||||
|  |         element.hotline = hotline; | ||||||
|  |  | ||||||
|  |         // Запись в счётчик успешных инициализаций | ||||||
|  |         success.add(hotline); | ||||||
|  |       } else ++error; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     if (event) { | ||||||
|  |       // Запрошен вызов события: "предварительная подготовка" | ||||||
|  |  | ||||||
|  |       // Вызов события: "предварительная подготовка" | ||||||
|  |       document.dispatchEvent( | ||||||
|  |         new CustomEvent(`hotline.preprocessed`, { | ||||||
|  |           detail: { | ||||||
|  |             success, | ||||||
|  |             error | ||||||
|  |           } | ||||||
|  |         }) | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | document.dispatchEvent( | ||||||
|  |   new CustomEvent("hotline.loaded", { | ||||||
|  |     detail: { hotline } | ||||||
|  |   }) | ||||||
|  | ); | ||||||
							
								
								
									
										2
									
								
								mirzaev/site/account/system/public/js/js.cookie.min.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,2 @@ | |||||||
|  | /*! js-cookie v3.0.1 | MIT */ | ||||||
|  | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}return function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"})})); | ||||||
							
								
								
									
										127
									
								
								mirzaev/site/account/system/public/js/trolling.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,127 @@ | |||||||
|  | "use strict"; | ||||||
|  |  | ||||||
|  | class troller { | ||||||
|  |     static what = { | ||||||
|  |         enable() { | ||||||
|  |             document.body.onmouseleave = function () { | ||||||
|  |                 // if (Math.random() > 0.90) { | ||||||
|  |                 // 10% | ||||||
|  |  | ||||||
|  |                 troller.what.start(); | ||||||
|  |                 // } | ||||||
|  |             }; | ||||||
|  |  | ||||||
|  |             document.body.onmouseenter = function () { | ||||||
|  |                 troller.what.end(); | ||||||
|  |             }; | ||||||
|  |         }, | ||||||
|  |         disable() { | ||||||
|  |             document.body.onmouseleave = document.body.onmouseenter = undefined; | ||||||
|  |         }, | ||||||
|  |         start() { | ||||||
|  |             // Отображение изображения | ||||||
|  |             document.getElementById('what_image').classList.add('active'); | ||||||
|  |  | ||||||
|  |             // Инициализация элемента со звуком | ||||||
|  |             const what_sound = document.getElementById('what_sound'); | ||||||
|  |  | ||||||
|  |             // Воспроизведение звука | ||||||
|  |             what_sound.currentTime = 0; | ||||||
|  |             what_sound.play(); | ||||||
|  |         }, | ||||||
|  |         end() { | ||||||
|  |             // Сокрытие изображения | ||||||
|  |             document.getElementById('what_image').classList.remove('active'); | ||||||
|  |  | ||||||
|  |             // Остановка звука | ||||||
|  |             document.getElementById('what_sound').pause(); | ||||||
|  |         }, | ||||||
|  |         single(event = 'onmouseleave') { | ||||||
|  |             if (typeof event === 'string') { | ||||||
|  |                 // Получены обязательные входные параметры | ||||||
|  |                 // Отображение изображения | ||||||
|  |                 document.getElementById('what_image').classList.add('active'); | ||||||
|  |  | ||||||
|  |                 // Инициализация элемента со звуком | ||||||
|  |                 const what_sound = document.getElementById('what_sound'); | ||||||
|  |  | ||||||
|  |                 // Воспроизведение звука | ||||||
|  |                 what_sound.currentTime = 0; | ||||||
|  |                 what_sound.play(); | ||||||
|  |  | ||||||
|  |                 document.body[event] = function () { | ||||||
|  |                     troller.what.end(); | ||||||
|  |  | ||||||
|  |                     document.body[event] = undefined; | ||||||
|  |                 }; | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static vk() { | ||||||
|  |         setInterval(function () { | ||||||
|  |             const sound = document.getElementById('sound_vk'); | ||||||
|  |  | ||||||
|  |             if (Math.random() > 0.95) { | ||||||
|  |                 // 5% | ||||||
|  |  | ||||||
|  |                 // Воспроизведение звука | ||||||
|  |                 sound.currentTime = 0; | ||||||
|  |                 sound.play(); | ||||||
|  |             } | ||||||
|  |         }, 85000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static whatsapp() { | ||||||
|  |         setInterval(function () { | ||||||
|  |             const sound = document.getElementById('sound_whatsup'); | ||||||
|  |  | ||||||
|  |             if (Math.random() > 0.97) { | ||||||
|  |                 // 3% | ||||||
|  |  | ||||||
|  |                 // Воспроизведение звука | ||||||
|  |                 sound.currentTime = 0; | ||||||
|  |                 sound.play(); | ||||||
|  |             } | ||||||
|  |         }, 125000); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     static iphone() { | ||||||
|  |         setInterval(function () { | ||||||
|  |             const sound = document.getElementById('sound_iphone'); | ||||||
|  |  | ||||||
|  |             if (Math.random() > 0.98) { | ||||||
|  |                 // 2% | ||||||
|  |  | ||||||
|  |                 // Воспроизведение звука | ||||||
|  |                 sound.currentTime = 0; | ||||||
|  |                 sound.play(); | ||||||
|  |             } | ||||||
|  |         }, 265000); | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (Math.random() > 0.90) { | ||||||
|  |     // 10% | ||||||
|  |  | ||||||
|  |     troller.what.enable(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (Math.random() > 0.90) { | ||||||
|  |     // 10% | ||||||
|  |  | ||||||
|  |     troller.vk(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  |  | ||||||
|  | if (Math.random() > 0.90) { | ||||||
|  |     // 10% | ||||||
|  |  | ||||||
|  |     troller.whatsapp(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | if (Math.random() > 0.90) { | ||||||
|  |     // 10% | ||||||
|  |  | ||||||
|  |     troller.iphone(); | ||||||
|  | } | ||||||
							
								
								
									
										1
									
								
								mirzaev/site/account/system/public/js/victor.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/sounds/iphone.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/sounds/vk.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/sounds/what.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										
											BIN
										
									
								
								mirzaev/site/account/system/public/sounds/whatsup.mp3
									
									
									
									
									
										Normal file
									
								
							
							
						
						| After Width: | Height: | Size: 16 KiB | 
							
								
								
									
										1
									
								
								mirzaev/site/account/system/settings/.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1 @@ | |||||||
|  | arangodb.php | ||||||
							
								
								
									
										8
									
								
								mirzaev/site/account/system/settings/arangodb.php.sample
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,8 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | return [ | ||||||
|  |     'endpoint' => 'unix:///var/run/arangodb3/arango.sock', | ||||||
|  |     'database' => '', | ||||||
|  |     'name' => '', | ||||||
|  |     'password' => '' | ||||||
|  | ]; | ||||||
							
								
								
									
										21
									
								
								mirzaev/site/account/system/views/account/element.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,21 @@ | |||||||
|  | {% block css %} | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/account.css"> | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/icon_authentication.css"> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | <section id="account"> | ||||||
|  |     {% if account %} | ||||||
|  |     {{ account.getKey() }} | ||||||
|  |     {% if vk %} | ||||||
|  |     {{ vk.mail }} | ||||||
|  |     {% endif %} | ||||||
|  |     {% else %} | ||||||
|  |     <button id="login" title="Войти в аккаунт" onclick="return account.authentication()"><i class='icon authentication'></i></button> | ||||||
|  |     {% endif %} | ||||||
|  | </section> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | <script type="text/javascript" src="/js/account.js"></script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										18
									
								
								mirzaev/site/account/system/views/aside.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,18 @@ | |||||||
|  | {% use 'account/element.html' with css as account_css, body as account_body, js as account_js %} | ||||||
|  |  | ||||||
|  | {% block css %} | ||||||
|  | {{ block('account_css') }} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | <aside> | ||||||
|  |     {{ block('account_body') }} | ||||||
|  | </aside> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | {{ block('account_js') }} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js_init %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										33
									
								
								mirzaev/site/account/system/views/core.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,33 @@ | |||||||
|  | <!doctype html> | ||||||
|  |  | ||||||
|  | <html lang="ru"> | ||||||
|  |  | ||||||
|  | <head> | ||||||
|  | 	{% use 'head.html' with title as head_title, meta as head_meta, css as head_css %} | ||||||
|  |  | ||||||
|  | 	{% block title %} | ||||||
|  | 	{{ block('head_title') }} | ||||||
|  | 	{% endblock %} | ||||||
|  |  | ||||||
|  | 	{% block meta %} | ||||||
|  | 	{{ block('head_meta') }} | ||||||
|  | 	{% endblock %} | ||||||
|  |  | ||||||
|  | 	{% block css %} | ||||||
|  | 	{{ block('head_css') }} | ||||||
|  | 	{% endblock %} | ||||||
|  | </head> | ||||||
|  |  | ||||||
|  | <body> | ||||||
|  | 	{% block body %} | ||||||
|  | 	{% endblock %} | ||||||
|  |  | ||||||
|  | 	{% block js %} | ||||||
|  | 	{% include 'js.html' %} | ||||||
|  | 	{% endblock %} | ||||||
|  |  | ||||||
|  | 	{% block js_init %} | ||||||
|  | 	{% endblock %} | ||||||
|  | </body> | ||||||
|  |  | ||||||
|  | </html> | ||||||
							
								
								
									
										4
									
								
								mirzaev/site/account/system/views/footer.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,4 @@ | |||||||
|  | <footer> | ||||||
|  |     <!-- <p><a href="http://www.anybrowser.org/campaign/"><img src="/img/logos/any_browser.gif" width="278" height="44" alt="Доступно на любом браузере" /></a></p> --> | ||||||
|  |     <!-- <p><a href="/browsers"><img src="/img/logos/any_browser.gif" width="278" height="44" alt="Доступно на любом браузере" /></a></p> --> | ||||||
|  | </footer> | ||||||
							
								
								
									
										86
									
								
								mirzaev/site/account/system/views/graph/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,86 @@ | |||||||
|  | {% block css %} | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/graph.css"> | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/icon_close.css" /> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block main %} | ||||||
|  | {% if graph.id != empty %} | ||||||
|  | <section id="{{ graph.id }}" class="graph unselectable" {% for name, value in graph.attributes %} {{ name }}="{{value}}" | ||||||
|  |     {% endfor %}> | ||||||
|  |     {% for element in graph.elements %} | ||||||
|  |     <{{element.tag??'article'}}>{{ element.content }}</{{element.tag??'article'}}> | ||||||
|  |     {% endfor %} | ||||||
|  | </section> | ||||||
|  | {% endif %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | <script type="module" src="/js/victor.js" defer></script> | ||||||
|  | <script type="module" src="/js/graph.js" defer></script> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js_init %} | ||||||
|  | <script> | ||||||
|  |     document.addEventListener('graph.loaded', function (e) { | ||||||
|  |         // Инициализация графика | ||||||
|  |         {% if graph.id != empty %} | ||||||
|  |         const core = new e.detail.graph(document.getElementById('{{ graph.id }}')); | ||||||
|  |  | ||||||
|  |         core.write({ | ||||||
|  |             title: 'бебра' | ||||||
|  |         }); | ||||||
|  |         const mirzaev = core.write({ | ||||||
|  |             title: 'Арсен Мирзаев', | ||||||
|  |             description: ' абабабаба абабабабаабабабабаабабабабаабабабаба абабабаба абабабаба абабабабаабабабаба абабабаба абабабаба абабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабабабабаба абабабабаабабаба', | ||||||
|  |             cover: 'https://sun9-east.userapi.com/sun9-27/s/v1/ig2/qOBvWvsDwPmMjOTqQbl0TuGHaoMwWQhhd81nxD847v32dT-pyYa9kxw2MY7moQBVBoN4iVLnUZx6WmE4x4HnIwAu.jpg?size=810x1080&quality=95&type=album', | ||||||
|  |             link: { | ||||||
|  |                 name: 'Арсен Мирзаев', | ||||||
|  |                 title: 'Читать статью полностью', | ||||||
|  |                 href: 'https://google.com', | ||||||
|  |                 class: ['source'] | ||||||
|  |             }, | ||||||
|  |             popup: 'Для подробной информации читайте статью полностью', | ||||||
|  |             color: 'red' | ||||||
|  |         }); | ||||||
|  |         const berbi = core.write({ | ||||||
|  |             title: 'берби' | ||||||
|  |         }); | ||||||
|  |         const anarchy = core.write({ | ||||||
|  |             title: 'анархия' | ||||||
|  |         }); | ||||||
|  |         core.connect( | ||||||
|  |             berbi, | ||||||
|  |             mirzaev); | ||||||
|  |         core.connect( | ||||||
|  |             anarchy, | ||||||
|  |             mirzaev); | ||||||
|  |         core.connect( | ||||||
|  |             core.write({ | ||||||
|  |                 title: 'бабы' | ||||||
|  |             }), | ||||||
|  |             mirzaev); | ||||||
|  |         core.connect( | ||||||
|  |             core.write({ | ||||||
|  |                 title: 'Ксения Велькович', | ||||||
|  |                 description: 'А меня вписать в кружочек?', | ||||||
|  |                 cover: 'https://storage.mirzaev.sexy/2022/mirzaev.sexy/nodes/ksenia_velkovich.jpg', | ||||||
|  |                 link: { | ||||||
|  |                     name: 'Ксения Велькович', | ||||||
|  |                     title: 'Страница ВКонтакте', | ||||||
|  |                     href: 'https://vk.com/id720261644', | ||||||
|  |                     class: ['source'] | ||||||
|  |                 } | ||||||
|  |             }), | ||||||
|  |             mirzaev); | ||||||
|  |         core.connect( | ||||||
|  |             core.write({ | ||||||
|  |                 title: 'чокопайки' | ||||||
|  |             }), | ||||||
|  |             mirzaev); | ||||||
|  |         core.connect( | ||||||
|  |             anarchy, | ||||||
|  |             berbi); | ||||||
|  |         {% endif %} | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										15
									
								
								mirzaev/site/account/system/views/head.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,15 @@ | |||||||
|  | {% block title %} | ||||||
|  | <title>{% if head.title != empty %}{{head.title}}{% else %}Мирзаев{% endif %}</title> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block meta %} | ||||||
|  | <meta charset="utf-8"> | ||||||
|  | <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> | ||||||
|  | {% for meta in head.metas %} | ||||||
|  | <meta {% for name, value in meta.attributes %}{{name}}="{{value}}" {% endfor %}> | ||||||
|  | {% endfor %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block css %} | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/main.css" /> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										13
									
								
								mirzaev/site/account/system/views/header.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,13 @@ | |||||||
|  | {% block css %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | <header> | ||||||
|  | </header> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js_init %} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										28
									
								
								mirzaev/site/account/system/views/hotline/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,28 @@ | |||||||
|  | {% block css %} | ||||||
|  | <link type="text/css" rel="stylesheet" href="/css/hotline.css"> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | {% if hotline.id != empty %} | ||||||
|  | <section id="{{ hotline.id }}" class="hotline unselectable" data-hotline="true" {% for name, value in hotline.parameters | ||||||
|  |     %} data-hotline-{{ name }}="{{value}}" {% endfor %} {% for name, value in hotline.attributes %} {{ name | ||||||
|  |     }}="{{value}}" {% endfor %}> | ||||||
|  |     {% for element in hotline.elements %} | ||||||
|  |     <{{element.tag??'article'}}>{{ element.content }}</{{element.tag??'article'}}> | ||||||
|  |     {% endfor %} | ||||||
|  | </section> | ||||||
|  | {% endif %} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | <script type="text/javascript" src="/js/hotline.js" defer></script> | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js_init %} | ||||||
|  | <script> | ||||||
|  |     document.addEventListener('hotline.loaded', function (e) { | ||||||
|  |         // Запуск препроцессора бегущих строк | ||||||
|  |         e.detail.hotline.preprocessing(); | ||||||
|  |     }); | ||||||
|  | </script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										42
									
								
								mirzaev/site/account/system/views/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,42 @@ | |||||||
|  | {% extends "core.html" %} | ||||||
|  |  | ||||||
|  | {% use "core.html" with css as core_css, body as core_body, js as core_js, js_init as core_js_init %} | ||||||
|  | {% use "header.html" with css as header_css, body as header_body, js as header_js, js_init as header_js_init %} | ||||||
|  | {% use "aside.html" with css as aside_css, body as aside_body, js as aside_js, js_init as aside_js_init %} | ||||||
|  | {% use 'graph/index.html' with css as graph_css, main as graph_main, js as graph_js, js_init as graph_js_init %} | ||||||
|  |  | ||||||
|  | {% block css %} | ||||||
|  | {{ block('core_css') }} | ||||||
|  | {{ block('header_css') }} | ||||||
|  | {{ block('aside_css') }} | ||||||
|  | {{ block('graph_css') }} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block body %} | ||||||
|  | {{ block('core_body') }} | ||||||
|  | {{ block('aside_body') }} | ||||||
|  | {{ block('header_body') }} | ||||||
|  |  | ||||||
|  | <main> | ||||||
|  | 	<noscript>К сожалению мой сайт ещё пока не готов для работы без javascript</noscript> | ||||||
|  | 	{% block main %} | ||||||
|  | {{ block('graph_main') }} | ||||||
|  | {% endblock %} | ||||||
|  | </main> | ||||||
|  |  | ||||||
|  | {# {% include 'footer.html' %} #} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js %} | ||||||
|  | {{ block('core_js') }} | ||||||
|  | {{ block('header_js') }} | ||||||
|  | {{ block('aside_js') }} | ||||||
|  | {{ block('graph_js') }} | ||||||
|  | {% endblock %} | ||||||
|  |  | ||||||
|  | {% block js_init %} | ||||||
|  | {{ block('core_js_init') }} | ||||||
|  | {{ block('header_js_init') }} | ||||||
|  | {{ block('aside_js_init') }} | ||||||
|  | {{ block('graph_js_init') }} | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										3
									
								
								mirzaev/site/account/system/views/js.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,3 @@ | |||||||
|  | {% block js %} | ||||||
|  | <script type="text/javascript" src="/js/js.cookie.min.js" defer></script> | ||||||
|  | {% endblock %} | ||||||
							
								
								
									
										25
									
								
								mirzaev/site/account/system/views/manager.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @@ -0,0 +1,25 @@ | |||||||
|  | <?php | ||||||
|  |  | ||||||
|  | declare(strict_types=1); | ||||||
|  |  | ||||||
|  | namespace mirzaev\site\account\views; | ||||||
|  |  | ||||||
|  | use mirzaev\minimal\controller; | ||||||
|  |  | ||||||
|  | use Twig\Loader\FilesystemLoader; | ||||||
|  | use Twig\Environment as view; | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Менеджер представлений | ||||||
|  |  * | ||||||
|  |  * @package mirzaev\site\account\controllers | ||||||
|  |  * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy> | ||||||
|  |  */ | ||||||
|  | final class manager extends controller | ||||||
|  | { | ||||||
|  |     public function render(string $file, array $vars = []): ?string | ||||||
|  |     { | ||||||
|  |         // Генерация представления | ||||||
|  |         return (new view(new FilesystemLoader(VIEWS)))->render($file, $vars); | ||||||
|  |     } | ||||||
|  | } | ||||||