Compare commits

..

No commits in common. "stable" and "1.1.x" have entirely different histories.

154 changed files with 3280 additions and 5393 deletions

View File

@ -5,7 +5,8 @@
"keywords": [
"site",
"api",
"authentication"
"authentication",
"auth"
],
"type": "site",
"homepage": "https://git.mirzaev.sexy/mirzaev/site-account",
@ -30,16 +31,15 @@
}
],
"require": {
"php": "~8.2",
"ext-sodium": "~8.2",
"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",
"scripturadesign/markov": "^2.0"
"guzzlehttp/guzzle": "^7.5"
},
"require-dev": {
"phpunit/phpunit": "~9.5"

203
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "c0621deb07173185d918fd02e7551ca4",
"content-hash": "b562666ddbd12600f288a2949acc97d4",
"packages": [
{
"name": "guzzlehttp/guzzle",
@ -396,13 +396,13 @@
"source": {
"type": "git",
"url": "https://git.mirzaev.sexy/mirzaev/accounts",
"reference": "aa93c4d26395025fa16bb65e8a40332ac352a742"
"reference": "4d6df00dc9538c99d8eced6deebab55f1af2bf8a"
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"guzzlehttp/guzzle": "^7.5",
"php": "~8.2"
"ext-dom": "20031129",
"ext-libxml": "~8.1.1",
"guzzlehttp/guzzle": "^7.2",
"php": "~8.1"
},
"require-dev": {
"phpdocumentor/phpdocumentor": ">=2.9",
@ -442,7 +442,7 @@
"type": "funding"
}
],
"time": "2023-02-17T08:36:36+00:00"
"time": "2022-11-05T23:48:10+00:00"
},
{
"name": "mirzaev/arangodb",
@ -931,69 +931,18 @@
},
"time": "2019-03-08T08:55:37+00:00"
},
{
"name": "scripturadesign/markov",
"version": "v2.0.0",
"source": {
"type": "git",
"url": "https://github.com/scripturadesign/markov.git",
"reference": "ca6d51a5d8ce1e115708c2d38c49c397e515cae9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/scripturadesign/markov/zipball/ca6d51a5d8ce1e115708c2d38c49c397e515cae9",
"reference": "ca6d51a5d8ce1e115708c2d38c49c397e515cae9",
"shasum": ""
},
"require": {
"php": "^8.0"
},
"require-dev": {
"friendsofphp/php-cs-fixer": "^3.4.0",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^4.15.0"
},
"type": "library",
"autoload": {
"psr-4": {
"Scriptura\\Markov\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Martin Dilling-Hansen",
"email": "martindilling@gmail.com",
"homepage": "http://martindilling.com",
"role": "Developer"
}
],
"description": "Markov Chain",
"homepage": "https://github.com/scripturadesign/markov",
"keywords": [
"markov chain"
],
"support": {
"issues": "https://github.com/scripturadesign/markov/issues",
"source": "https://github.com/scripturadesign/markov/tree/v2.0.0"
},
"time": "2021-12-15T00:09:01+00:00"
},
{
"name": "symfony/deprecation-contracts",
"version": "v3.2.1",
"version": "v3.1.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/deprecation-contracts.git",
"reference": "0121954c80fd17c18cf050fe73360e63bb43d4fb"
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/0121954c80fd17c18cf050fe73360e63bb43d4fb",
"reference": "0121954c80fd17c18cf050fe73360e63bb43d4fb",
"url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"reference": "07f1b9cc2ffee6aaafcf4b710fbc38ff736bd918",
"shasum": ""
},
"require": {
@ -1002,7 +951,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-main": "3.3-dev"
"dev-main": "3.1-dev"
},
"thanks": {
"name": "symfony/contracts",
@ -1031,7 +980,7 @@
"description": "A generic function and convention to trigger deprecation notices",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.2.1"
"source": "https://github.com/symfony/deprecation-contracts/tree/v3.1.1"
},
"funding": [
{
@ -1047,7 +996,7 @@
"type": "tidelift"
}
],
"time": "2023-02-02T07:48:03+00:00"
"time": "2022-02-25T11:15:52+00:00"
},
{
"name": "symfony/polyfill-ctype",
@ -1277,16 +1226,16 @@
},
{
"name": "twig/twig",
"version": "v3.5.1",
"version": "v3.4.3",
"source": {
"type": "git",
"url": "https://github.com/twigphp/Twig.git",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15"
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/a6e0510cc793912b451fd40ab983a1d28f611c15",
"reference": "a6e0510cc793912b451fd40ab983a1d28f611c15",
"url": "https://api.github.com/repos/twigphp/Twig/zipball/c38fd6b0b7f370c198db91ffd02e23b517426b58",
"reference": "c38fd6b0b7f370c198db91ffd02e23b517426b58",
"shasum": ""
},
"require": {
@ -1301,7 +1250,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "3.5-dev"
"dev-master": "3.4-dev"
}
},
"autoload": {
@ -1337,7 +1286,7 @@
],
"support": {
"issues": "https://github.com/twigphp/Twig/issues",
"source": "https://github.com/twigphp/Twig/tree/v3.5.1"
"source": "https://github.com/twigphp/Twig/tree/v3.4.3"
},
"funding": [
{
@ -1349,36 +1298,36 @@
"type": "tidelift"
}
],
"time": "2023-02-08T07:49:20+00:00"
"time": "2022-09-28T08:42:51+00:00"
}
],
"packages-dev": [
{
"name": "doctrine/instantiator",
"version": "2.0.0",
"version": "1.4.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/instantiator.git",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0"
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"reference": "c6222283fa3f4ac679f8b9ced9a4e23f163e80d0",
"url": "https://api.github.com/repos/doctrine/instantiator/zipball/10dcfce151b967d20fde1b34ae6640712c3891bc",
"reference": "10dcfce151b967d20fde1b34ae6640712c3891bc",
"shasum": ""
},
"require": {
"php": "^8.1"
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^11",
"doctrine/coding-standard": "^9",
"ext-pdo": "*",
"ext-phar": "*",
"phpbench/phpbench": "^1.2",
"phpstan/phpstan": "^1.9.4",
"phpstan/phpstan-phpunit": "^1.3",
"phpunit/phpunit": "^9.5.27",
"vimeo/psalm": "^5.4"
"phpbench/phpbench": "^0.16 || ^1",
"phpstan/phpstan": "^1.4",
"phpstan/phpstan-phpunit": "^1",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"vimeo/psalm": "^4.22"
},
"type": "library",
"autoload": {
@ -1405,7 +1354,7 @@
],
"support": {
"issues": "https://github.com/doctrine/instantiator/issues",
"source": "https://github.com/doctrine/instantiator/tree/2.0.0"
"source": "https://github.com/doctrine/instantiator/tree/1.4.1"
},
"funding": [
{
@ -1421,7 +1370,7 @@
"type": "tidelift"
}
],
"time": "2022-12-30T00:23:10+00:00"
"time": "2022-03-03T08:28:38+00:00"
},
{
"name": "myclabs/deep-copy",
@ -1484,16 +1433,16 @@
},
{
"name": "nikic/php-parser",
"version": "v4.15.3",
"version": "v4.15.2",
"source": {
"type": "git",
"url": "https://github.com/nikic/PHP-Parser.git",
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039"
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"reference": "570e980a201d8ed0236b0a62ddf2c9cbb2034039",
"url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"reference": "f59bbe44bf7d96f24f3e2b4ddc21cd52c1d2adbc",
"shasum": ""
},
"require": {
@ -1534,9 +1483,9 @@
],
"support": {
"issues": "https://github.com/nikic/PHP-Parser/issues",
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.3"
"source": "https://github.com/nikic/PHP-Parser/tree/v4.15.2"
},
"time": "2023-01-16T22:05:37+00:00"
"time": "2022-11-12T15:38:23+00:00"
},
{
"name": "phar-io/manifest",
@ -1651,23 +1600,23 @@
},
{
"name": "phpunit/php-code-coverage",
"version": "9.2.25",
"version": "9.2.19",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/php-code-coverage.git",
"reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954"
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/0e2b40518197a8c0d4b08bc34dfff1c99c508954",
"reference": "0e2b40518197a8c0d4b08bc34dfff1c99c508954",
"url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/c77b56b63e3d2031bd8997fcec43c1925ae46559",
"reference": "c77b56b63e3d2031bd8997fcec43c1925ae46559",
"shasum": ""
},
"require": {
"ext-dom": "*",
"ext-libxml": "*",
"ext-xmlwriter": "*",
"nikic/php-parser": "^4.15",
"nikic/php-parser": "^4.14",
"php": ">=7.3",
"phpunit/php-file-iterator": "^3.0.3",
"phpunit/php-text-template": "^2.0.2",
@ -1716,7 +1665,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/php-code-coverage/issues",
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.25"
"source": "https://github.com/sebastianbergmann/php-code-coverage/tree/9.2.19"
},
"funding": [
{
@ -1724,7 +1673,7 @@
"type": "github"
}
],
"time": "2023-02-25T05:32:00+00:00"
"time": "2022-11-18T07:47:47+00:00"
},
{
"name": "phpunit/php-file-iterator",
@ -1969,20 +1918,20 @@
},
{
"name": "phpunit/phpunit",
"version": "9.6.4",
"version": "9.5.26",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/phpunit.git",
"reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d"
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9125ee085b6d95e78277dc07aa1f46f9e0607b8d",
"reference": "9125ee085b6d95e78277dc07aa1f46f9e0607b8d",
"url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"reference": "851867efcbb6a1b992ec515c71cdcf20d895e9d2",
"shasum": ""
},
"require": {
"doctrine/instantiator": "^1.3.1 || ^2",
"doctrine/instantiator": "^1.3.1",
"ext-dom": "*",
"ext-json": "*",
"ext-libxml": "*",
@ -2020,7 +1969,7 @@
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "9.6-dev"
"dev-master": "9.5-dev"
}
},
"autoload": {
@ -2051,7 +2000,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/phpunit/issues",
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.6.4"
"source": "https://github.com/sebastianbergmann/phpunit/tree/9.5.26"
},
"funding": [
{
@ -2067,7 +2016,7 @@
"type": "tidelift"
}
],
"time": "2023-02-27T13:06:37+00:00"
"time": "2022-10-28T06:00:21+00:00"
},
{
"name": "sebastian/cli-parser",
@ -2435,16 +2384,16 @@
},
{
"name": "sebastian/environment",
"version": "5.1.5",
"version": "5.1.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/environment.git",
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed"
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
"reference": "830c43a844f1f8d5b7a1f6d6076b784454d8b7ed",
"url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/1b5dff7bb151a4db11d49d90e5408e4e938270f7",
"reference": "1b5dff7bb151a4db11d49d90e5408e4e938270f7",
"shasum": ""
},
"require": {
@ -2486,7 +2435,7 @@
],
"support": {
"issues": "https://github.com/sebastianbergmann/environment/issues",
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.5"
"source": "https://github.com/sebastianbergmann/environment/tree/5.1.4"
},
"funding": [
{
@ -2494,7 +2443,7 @@
"type": "github"
}
],
"time": "2023-02-03T06:03:51+00:00"
"time": "2022-04-03T09:37:03+00:00"
},
{
"name": "sebastian/exporter",
@ -2808,16 +2757,16 @@
},
{
"name": "sebastian/recursion-context",
"version": "4.0.5",
"version": "4.0.4",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/recursion-context.git",
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1"
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"reference": "e75bd0f07204fec2a0af9b0f3cfe97d05f92efc1",
"url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/cd9d8cf3c5804de4341c283ed787f099f5506172",
"reference": "cd9d8cf3c5804de4341c283ed787f099f5506172",
"shasum": ""
},
"require": {
@ -2856,10 +2805,10 @@
}
],
"description": "Provides functionality to recursively process PHP variables",
"homepage": "https://github.com/sebastianbergmann/recursion-context",
"homepage": "http://www.github.com/sebastianbergmann/recursion-context",
"support": {
"issues": "https://github.com/sebastianbergmann/recursion-context/issues",
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.5"
"source": "https://github.com/sebastianbergmann/recursion-context/tree/4.0.4"
},
"funding": [
{
@ -2867,7 +2816,7 @@
"type": "github"
}
],
"time": "2023-02-03T06:07:39+00:00"
"time": "2020-10-26T13:17:30+00:00"
},
{
"name": "sebastian/resource-operations",
@ -2926,16 +2875,16 @@
},
{
"name": "sebastian/type",
"version": "3.2.1",
"version": "3.2.0",
"source": {
"type": "git",
"url": "https://github.com/sebastianbergmann/type.git",
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7"
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"reference": "75e2c2a32f5e0b3aef905b9ed0b179b953b3d7c7",
"url": "https://api.github.com/repos/sebastianbergmann/type/zipball/fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"reference": "fb3fe09c5f0bae6bc27ef3ce933a1e0ed9464b6e",
"shasum": ""
},
"require": {
@ -2970,7 +2919,7 @@
"homepage": "https://github.com/sebastianbergmann/type",
"support": {
"issues": "https://github.com/sebastianbergmann/type/issues",
"source": "https://github.com/sebastianbergmann/type/tree/3.2.1"
"source": "https://github.com/sebastianbergmann/type/tree/3.2.0"
},
"funding": [
{
@ -2978,7 +2927,7 @@
"type": "github"
}
],
"time": "2023-02-03T06:13:03+00:00"
"time": "2022-09-12T14:47:03+00:00"
},
{
"name": "sebastian/version",
@ -3094,8 +3043,8 @@
"prefer-stable": false,
"prefer-lowest": false,
"platform": {
"php": "~8.2",
"ext-sodium": "~8.2"
"php": "~8.1",
"ext-sodium": "~8.1"
},
"platform-dev": [],
"plugin-api-version": "2.3.0"

View File

@ -1,28 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\controllers;
// Файлы проекта
use mirzaev\site\account\controllers\core,
mirzaev\site\account\models\account as model;
/**
* Контроллер аккаунта
*
* @package mirzaev\site\account\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class account extends core
{
/**
* Страница профиля
*
* @param array $parameters Параметры запроса
*/
public function index(array $parameters = []): ?string
{
return null;
}
}

View 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);
}
}

View File

@ -1,73 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\controllers;
// Файлы проекта
use mirzaev\site\account\controllers\core,
mirzaev\site\account\controllers\traits\errors,
mirzaev\site\account\models\generators\password;
// Встроенные библиотеки
use exception;
/**
* Контроллер API
*
* @package mirzaev\site\account\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class api extends core
{
use errors;
/**
* Сгенерировать пароль
*
* @param array $parameters Параметры запроса
*
* @return string JSON-документ с запрашиваемыми параметрами
*/
public function password(array $parameters = []): string
{
// Инициализация буфера ответа
$buffer = [];
// Инициализация реестра возвращаемых параметров
$return = explode(',', $parameters['return'], 50);
// Инициализация значений по умолчению
$parameters['length'] ??= 6;
$parameters['type'] ??= 'classic';
try {
// Проверка параметров на соответствие требованиям
if (($parameters['length'] = (int) $parameters['length']) === 0) throw new exception('Минимальная длина генерируемого пароля: 1 символ');
if ($parameters['type'] !== 'classic' && $parameters['type'] !== 'mnemonic') throw new exception('Допустимые типы пароля: "mnemonic", "classic"');
// Генерация ответа по запрашиваемым параметрам
foreach ($return as $parameter) match ($parameter) {
'password' => $buffer['password'] = password::{$parameters['type'] ?? 'classic'}($parameters['length'], $this->errors),
'errors' => null,
default => throw new exception("Параметр не найден: $parameter")
};
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Запись реестра ошибок в буфер ответа
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
// Запись заголовка ответа
header('Content-Type: application/json');
return json_encode($buffer);
}
}

View File

@ -5,16 +5,20 @@ declare(strict_types=1);
namespace mirzaev\site\account\controllers;
// Файлы проекта
use mirzaev\site\account\views\templater,
mirzaev\site\account\models\core as models,
mirzaev\site\account\models\account,
mirzaev\site\account\models\session;
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 exception;
// Фреймворк ВКонтакте
use mirzaev\vk\core as vk;
use mirzaev\vk\robots\user as robot;
/**
* Ядро контроллеров
@ -25,57 +29,45 @@ use exception;
class core extends controller
{
/**
* Инстанция сессии
* Переменные окружения
*/
public session $session;
protected robot $vk;
/**
* Инстанция аккаунта
* Переменные окружения
*/
public ?account $account;
/**
* Постфикс
*/
public string $postfix = '';
/**
* Реестр ошибок
*/
public array $errors = [
'session' => [],
'account' => []
];
protected array $variables = [];
/**
* Конструктор
*
* @param bool $initialize Инициализировать контроллер?
* @return void
*/
public function __construct(bool $initialize = true)
public function __construct()
{
parent::__construct($initialize);
if ($initialize) {
// Запрошена инициализация
parent::__construct();
// Инициализация ядра моделей (соединение с базой данных...)
new models();
// Инициализация журнала ошибок
$this->variables['errors'] = [
'session' => [],
'account' => [],
'vk' => []
];
// Инициализация даты до которой будет активна сессия
$expires = time() + 604800;
// Инициализация значения по умолчанию
$_COOKIE["session"] ??= null;
// Инициализация сессии (без журналирования)
$this->variables['session'] = session::initialization($_COOKIE["session"] ?? null, $expires) ?? header('Location: https://mirzaev.sexy/error?code=500&text=Не+удалось+инициализировать+сессию');
// Инициализация сессии
$this->session = new session($_COOKIE["session"], $expires);
if ($_COOKIE["session"] !== $this->session->hash) {
if ($_COOKIE["session"] ?? null !== $this->variables['session']->hash) {
// Изменился хеш сессии (подразумевается, что сессия устарела)
// Запись хеша новой сессии
setcookie('session', $this->session->hash, [
setcookie('session', $this->variables['session']->hash, [
'expires' => $expires,
'domain' => 'mirzaev.sexy',
'path' => '/',
@ -85,11 +77,24 @@ class core extends controller
]);
}
// Инициализация аккаунта
$this->account = new account($this->session);
// Инициализация аккаунта (без журналирования)
$this->variables['account'] = session::account($this->variables['session']);
// Инициализация шаблонизатора представлений
$this->view = new templater($this->session, $this->account);
}
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;
}
}

View File

@ -13,7 +13,7 @@ use mirzaev\site\account\controllers\core;
* @package mirzaev\site\account\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class error extends core
final class error_controller extends core
{
/**
* Страница с ошибкой
@ -23,22 +23,22 @@ final class error extends core
public function index(array $parameters = []): ?string
{
// Запись текста ошибки в переменную окружения
$this->view->text = $parameters['text'] ?? null;
$this->variables['text'] = $parameters['text'] ?? null;
if (isset($parameters['code'])) {
// Получен код ошибки
// Запись кода ошибки в переменную окружения
$this->view->code = $parameters['code'];
$this->variables['code'] = $parameters['code'];
// Запись кода ответа
http_response_code($parameters['code']);
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . 'index.html');
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');
return $this->view->render(DIRECTORY_SEPARATOR . 'errors' . DIRECTORY_SEPARATOR . ($parameters['code'] ?? 'index') . '.html', $this->variables);
}
}

View File

@ -13,7 +13,7 @@ use mirzaev\site\account\controllers\core;
* @package mirzaev\site\account\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class graph extends core
final class graph_controller extends core
{
/**
* Страница с графиком

View File

@ -1,79 +0,0 @@
<?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 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);
}
}

View File

@ -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);
}
}

View File

@ -1,36 +0,0 @@
<?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 extends core
{
/**
* Главная страница
*
* @param array $parameters Параметры запроса
*/
public function index(array $parameters = []): ?string
{
// Инициализация узлов
$this->view->nodes = [
'account' => $this->view->render(DIRECTORY_SEPARATOR . (isset($this->account->document)
? 'nodes' . DIRECTORY_SEPARATOR . 'profile.html'
: 'pages' . DIRECTORY_SEPARATOR . 'entry.html'))
/* 'account' => $this->view->render(DIRECTORY_SEPARATOR . 'nodes' . DIRECTORY_SEPARATOR . (isset($this->account) ? 'profile.html' : 'connect.html')) */
];
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'index.html');
}
}

View 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);
}
}

View File

@ -1,268 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\controllers;
// Файлы проекта
use mirzaev\site\account\controllers\core,
mirzaev\site\account\controllers\traits\errors,
mirzaev\site\account\models\invite,
mirzaev\site\account\models\account;
// Встроенные библиотеки
use exception;
/**
* Контроллер сессии
*
* @package mirzaev\site\account\controllers
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class session extends core
{
use errors;
/**
* Записать входной псевдоним в буфер сессии
*
* Проверяет существование аккаунта с этим входным псевдонимом
* и запоминает для использования в процессе аутентификации
*
* @param array $parameters Параметры запроса
*
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
*/
public function login(array $parameters = []): void
{
// Инициализация буфера ответа
$buffer = [];
// Инициализация реестра возвращаемых параметров
$return = explode(',', $parameters['return'], 50);
try {
// Проверка наличия обязательных параметров
if (empty($parameters['login'])) throw new exception('Необходимо передать входной псевдоним');
// Вычисление длины
$length = strlen($parameters['login']);
// Проверка параметров на соответствование требованиям
if ($length === 0) throw new exception('Входной псевдоним не может быть пустым');
if ($length > 100) throw new exception('Входной псевдоним не может быть длиннее 100 символов');
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['login'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
// Поиск аккаунта
$account = account::login($parameters['login']);
// Генерация ответа по запрашиваемым параметрам
foreach ($return as $parameter) match ($parameter) {
'exist' => $buffer['exist'] = isset($account->document),
'account' => (function () use ($parameters, &$buffer) {
// Запись в буфер сессии
if (isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['login' => $parameters['login']]], $this->errors);
// Поиск аккаунта и запись в буфер вывода
$buffer['account'] = isset((new account($this->session, authenticate: true, errors: $this->errors))->document);
})(),
'errors' => null,
default => throw new exception("Параметр не найден: $parameter")
};
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors['session'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Запись реестра ошибок в буфер ответа
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Генерация ответа
echo json_encode($buffer);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
// Запись в буфер сессии
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['login' => $parameters['login']]]);
}
/**
* Записать пароль в буфер сессии
*
* Проверяет на соответствие требованиям
* и запоминает для использования в процессе аутентификации
*
* @param array $parameters Параметры запроса
*
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
*/
public function password(array $parameters = []): void
{
// Инициализация буфера ответа
$buffer = [];
// Инициализация реестра возвращаемых параметров
$return = explode(',', $parameters['return'], 50);
try {
// Вычисление длины
$length = strlen($parameters['password']);
// Проверка параметров на соответствование требованиям
if ($length > 300) throw new exception('Пароль не может быть длиннее 300 символов');
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['password'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
// Генерация ответа по запрашиваемым параметрам
foreach ($return as $parameter) match ($parameter) {
'verify' => $buffer['verify'] = true,
'account' => (function() use ($parameters, &$buffer) {
// Запись в буфер сессии
if (isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['password' => $parameters['password']]], $this->errors);
// Поиск аккаунта и запись в буфер вывода
$buffer['account'] = isset((new account($this->session, authenticate: true, register: true, errors: $this->errors))->document);
})(),
'errors' => null,
default => throw new exception("Параметр не найден: $parameter")
};
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors['session'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Запись реестра ошибок в буфер ответа
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Генерация ответа
echo json_encode($buffer);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
// Запись в буфер сессии
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['password' => $parameters['password']]]);
}
/**
* Записать код приглашения в буфер сессии
*
* Проверяет существование приглашения с этим кодом
* и запоминает для использования в процессе регистрации
*
* @param array $parameters Параметры запроса
*
* @return void В буфер вывода JSON-документ с запрашиваемыми параметрами
*/
public function invite(array $parameters = []): void
{
// Инициализация буфера ответа
$buffer = [];
// Инициализация реестра возвращаемых параметров
$return = explode(',', $parameters['return'], 50);
try {
// Проверка наличия обязательных параметров
if (empty($parameters['invite'])) throw new exception('Необходимо передать ключ приглашения');
// Вычисление длины
$length = strlen($parameters['invite']);
// Проверка параметров на соответствование требованиям
if ($length === 0) throw new exception('Получен пустой ключ приглашения');
if (preg_match_all('/[^\w\s\r\n\t\0]+/u', $parameters['invite'], $matches) > 0) throw new exception('Нельзя использовать символы: ' . implode(', ', ...$matches));
// Поиск приглашения
$invite = invite::read($parameters['invite']);
// Генерация ответа по запрашиваемым параметрам
foreach ($return as $parameter) match ($parameter) {
'exist' => $buffer['exist'] = isset($invite->document),
// from временное решение пока не будет разработана система сессий
'from' => $buffer['from'] = ['login' => 'mirzaev'] ?? $invite->from(),
'account' => (function () use ($parameters, &$buffer) {
// Запись в буфер сессии
if (isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['invite' => $parameters['invite']]], $this->errors);
// Поиск аккаунта и запись в буфер вывода
$buffer['account'] = isset((new account($this->session, authenticate: true, errors: $this->errors))->document);
})(),
'errors' => null,
default => throw new exception("Параметр не найден: $parameter")
};
} catch (exception $e) {
// Запись в реестр ошибок
$this->errors['session'][] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
// Запись реестра ошибок в буфер ответа
if (in_array('errors', $return, true)) $buffer['errors'] = self::parse_only_text($this->errors);
// Запись заголовков ответа
header('Content-Type: application/json');
header('Content-Encoding: none');
header('X-Accel-Buffering: no');
// Инициализация буфера вывода
ob_start();
// Генерация ответа
echo json_encode($buffer);
// Запись заголовков ответа
header('Content-Length: ' . ob_get_length());
// Отправка и деинициализация буфера вывода
ob_end_flush();
flush();
// Запись в буфер сессии
if (!in_array('account', $return, true) && isset($parameters['remember']) && $parameters['remember'] === '1')
$this->session->write(['entry' => ['invite' => $parameters['invite']]]);
}
}

View File

@ -1,30 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\controllers\traits;
/**
* Заготовка для обработки ошибок
*
* @package mirzaev\site\account\controllers\traits
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
trait errors
{
private static function parse_only_text(array $errors): array
{
// Инициализация буфера вывода
$buffer = [];
foreach ($errors as $offset => $error) {
// Перебор ошибок
// Проверка на вложенность и запись в буфер вывода (вход в рекурсию)
if (isset($error['text'])) $buffer[] = $error['text'];
else if (is_array($error) && count($error) > 0) $buffer[$offset] = static::parse_only_text($error);
}
return $buffer;
}
}

View File

@ -1,322 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models;
// Фреймворк 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 extends core
{
/**
* Коллекция
*/
public const COLLECTION = 'account';
/**
* Инстанция документа аккаунта в базе данных
*/
public ?_document $document;
/**
* Конструктор
*
* 1. Проверяет связь сессии с аккаунтом
* 1.1. Если найдена связь, то возвращает связанный аккаунт (выход)
* 2. [authenticate === true] Проверяет наличие данных в буфере сессии
* 2.1 Если найден входной псевдоним и пароли совпадают, то аутентифицирует (выход)
* 2.2 [register === true] Если найдены данные для регистрации, то регистрирует (выход)
*
* @param ?session $session Инстанция сессии
* @param bool $authenticate Аутентифицировать аккаунт?
* @param bool $register Регистрировать аккаунт?
* @param array &$errors Реестр ошибок
*
* @return static Инстанция аккаунта
*/
public function __construct(?session $session = null, bool $authenticate = false, bool $register = false, array &$errors = [])
{
try {
if (isset($session)) {
// Получена инстанция сессии
if ($account = $session->account()) {
// Найден связанный с сессией аккаунт
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
return $this;
} else {
// Не найден связанный с сессией аккаунт
if ($authenticate) {
// Запрошена аутентификация
if (!empty($session->buffer['entry'])) {
// Найдены данные для идентификации в буфере сессии
if (!empty($session->buffer['entry']['login'])) {
// Найдены входной псевдоним в буфере сессии
if (($account = self::login($session->buffer['entry']['login'])) instanceof self) {
// Найден аккаунт (игнорируются ошибки)
if (isset($account->password) && $account->password === '') {
// Не имеет пароля аккаунт
// Проверка отсутствия переданного пароля
if (isset($session->buffer['entry']['password']) && $session->buffer['entry']['password'] !== '') throw new exception('Неправильный пароль');
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null]]);
return $this;
} else if (!empty($session->buffer['entry']['password'])) {
// Найден пароль в буфере сессии
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['entry']['password'])) {
// Аутентифицирован аккаунт (прошёл проверку пароль, либо аккаунт не имеет пароля)
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null]]);
return $this;
} else throw new exception('Неправильный пароль');
} throw new exception('Неправильный пароль');
} else {
// Не найден аккаунт
if ($register) {
// Запрошена регистрация
if (!empty($session->buffer['entry']['invite'])) {
// Найден ключ приглашения в буфере сессии
// Проверка наличия переданного пароля
if (!isset($session->buffer['entry']['password'])) throw new exception('Не найден пароль в буфере сессии');
if (self::create(
[
'login' => $session->buffer['entry']['login'],
'password' => $session->buffer['entry']['password'] === ''
? ''
: sodium_crypto_pwhash_str(
$session->buffer['entry']['password'],
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
)
],
$errors
)) {
// Зарегистрирован аккаунт
if (($account = self::login($session->buffer['entry']['login'], $errors)) instanceof self) {
// Найден аккаунт
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null, 'invite' => null]]);
return $this;
} else throw new exception('Не удалось аутентифицировать аккаунт после его регистрации');
} else throw new exception('Не удалось зарегистрировать аккаунт');
} else throw new exception('Не найден ключ приглашения в буфере сессии');
}
}
} else throw new exception('Не найден входной псевдоним в буфере сессии');
} else throw new exception('Не найдены данные для идентификации');
}
}
}
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
/**
* Найти по входному псевдониму
*
* @param string $login Входной псевдоним
* @param array &$errors Реестр ошибок
*
* @return ?self Инстанция аккаунта, если аутентифицирован
*/
public static function login(string $login, array &$errors = []): ?self
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Инициализация инстанции аккаунта
$instance = new self;
// Поиск инстанции аккаунта в базе данных
$instance->document = collection::search(
static::$db->session,
sprintf(
<<<'AQL'
FOR d IN %s
FILTER d.login == '%s'
RETURN d
AQL,
self::COLLECTION,
$login
)
);
if ($instance->document instanceof _document) return $instance;
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Создать
*
* @param array $data Данные аккаунта
* @param array &$errors Реестр ошибок
*
* @return bool Создан аккаунт?
*/
public static function create(array $data = [], array &$errors = []): bool
{
try {
if (collection::init(static::$db->session, self::COLLECTION))
if (document::write(static::$db->session, self::COLLECTION, $data)) return true;
else throw new exception('Не удалось создать аккаунт');
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Записать
*
* Записывает свойство в инстанцию документа аккаунта из базы данных
*
* @param string $name Название
* @param mixed $value Содержимое
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
$this->document->{$name} = $value;
}
/**
* Прочитать
*
* Читает свойство из инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных
*/
public function __get(string $name): mixed
{
return $this->document->{$name};
}
/**
* Проверить инициализированность
*
* Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return bool Свойство инициализировано?
*/
public function __isset(string $name): bool
{
return isset($this->document->{$name});
}
/**
* Удалить
*
* Деинициализировать свойство в инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
unset($this->document->{$name});
}
/**
* Выполнить метод
*
* Выполнить метод в инстанции документа аккаунта из базы данных
*
* @param string $name Название
* @param array $arguments Аргументы
*/
public function __call(string $name, array $arguments = [])
{
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
}
}

View 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;
}
}

View File

@ -23,29 +23,13 @@ class core extends model
*/
public const SETTINGS = '../settings/arangodb.php';
/**
* Постфикс
*/
public string $postfix = '';
/**
* Соединение с базой данных
*/
protected static connection $db;
/**
* Конструктор
*
* @param bool $initialize Инициализировать контроллер?
* @param connection $db Инстанция соединения с базой данных
*/
public function __construct(bool $initialize = true, connection $db = null)
public function __construct(connection $db = null)
{
parent::__construct($initialize);
if ($initialize) {
// Запрошена инициализация
if (isset($db)) {
// Получена инстанция соединения с базой данных
@ -58,7 +42,6 @@ class core extends model
$this->__get('db');
}
}
}
/**
* Записать свойство

View File

@ -1,159 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models\generators;
// Файлы проекта
use mirzaev\site\account\models\core;
// Генератор по модели цепи Маркова
use Scriptura\Markov\Chain as chain,
Scriptura\Markov\Generator as generator,
Scriptura\Markov\RNG\RandomIntRNG as rng;
// Встроенные библиотеки
use exception;
/**
* Модель генератора паролей
*
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class password extends core
{
/**
* Сгенерировать мнемонический пароль
*
* @param int $length Длина (количество слов)
* @param array &$errors Реестр ошибок
*
* @return ?string Пароль
*/
public static function mnemonic(int $length = 4, array &$errors = []): ?string
{
try {
preg_match_all(
'/\w+/um',
mb_convert_encoding(
<<<TEXT
Коридор был пуст. Мгновение я постоял перед закрытой дверью, прислушиваясь. Стены, наверно, были тонкими, снаружи сквозь них проникал плач ветра. На двери, немного наискось, висел небрежно прикрепленный прямоугольный кусок пластыря с карандашной надписью «Человек». Неразборчиво нацарапанное слово вызвало у меня желание вернуться к Снауту, но я понял, что это невозможно.
Нелепое предостережение все еще звучало в ушах. Тихо, как будто бессознательно скрываясь от невидимого наблюдателя, я вернулся в круглую камеру с пятью дверьми. На трех из них висели таблички: «Д-р Гибарян», «Д-р Снаут», «Д-р Сарториус». На четвертой таблички не было. Поколебавшись, я нажал ручку. Пока дверь медленно открывалась, у меня появилось граничащее с уверенностью ощущение, что в комнате кто-то есть. Я вошел внутрь.
В комнате никого не было. Выпуклое окно глядело на океан, который жирно блестел под солнцем, как будто с волн стекало красное масло. Пурпурный отблеск заливал комнату, похожую на корабельную каюту. С одной стороны ее находились полки с книгами и прикрепленная вертикально к стене кровать в карданной подвеске. С другой было очень много шкафчиков. Между ними в никелированных рамках висели фотоснимки планеты. В металлических захватах торчали колбы и пробирки, заткнутые ватой. Под окном в два ряда громоздились белые эмалированные ящики с инструментами. В углах комнаты краны, вытяжной шкаф, холодильные установки, на полу стоял микроскоп, для него уже не было места на большом столе у окна.
Я обернулся и около входной двери увидел шкаф с открытыми дверцами до самого потолка. В нем висели комбинезоны, рабочие и защитные халаты, на полках белье, между голенищами противорадиационных сапог поблескивали алюминиевые баллоны для переносных кислородных аппаратов. Два аппарата с масками болтались на поручне поднятой кровати. Везде был тот же кое-как упорядоченный хаос.
Я втянул воздух и почувствовал слабый запах химических реактивов. Машинально поискал глазами вентиляционные решетки. Прикрепленные к ним полоски бумаги легонько колебались, показывая, что компрессоры работают, поддерживая нормальный обмен воздуха. Я перенес книги, аппараты и инструменты с двух кресел в углы, распихал все это как попало, и вокруг постели, между шкафом и полками, образовалось относительно пустое пространство. Потом подтянул вешалку, чтобы повесить на нее скафандр, и уже взялся за замки-молнии, но тут же их отпустил. Я никак не мог решиться снять скафандр, как будто от этого стал бы беззащитным. Еще раз я окинул взглядом комнату. Дверь была плотно закрыта, но замка в ней не было, и после недолгого колебания я припер ее двумя самыми тяжелыми ящиками.
Забаррикадировавшись так, я освободился от своей скрипящей оболочки. Узкое зеркало на внутренней поверхности шкафа отражало часть комнаты. Углом глаза я заметил какое-то движение, вскочил, но тут же понял, что это мое собственное отражение. Комбинезон под скафандром пропотел. Я сбросил его и толкнул шкаф. Он отъехал в сторону, и в нише за ним заблестели стены миниатюрной ванной комнаты. На полу, под душем, лежал довольно большой плоский ящик, который я с трудом втащил в комнату. Когда я опускал его на пол, крышка отскочила как на пружине, и я увидел отделения, набитые странными предметами. Ящик был полон страшно изуродованных инструментов из темного металла, немного похожих на те, которые лежали в шкафах. Все они никуда не годились, бесформенные, скрученные, оплавленные, словно вынесенные из пожара. Самым удивительным было то, что повреждения такого же характера были даже на керамитовых, то есть практически не плавящихся, рукоятках. Ни в одной лабораторной печи нельзя было получить температуру, при которой они бы плавились, разве что внутри атомного котла. Из кармана моего скафандра я достал портативный дозиметр, но черный цилиндрик молчал, когда я поднес его к обломкам.
На мне были только трусы и рубашка-сетка. Я скинул их на пол и пошел под душ. Вода принесла облегчение. Я изгибался под потоком твердых горячих струй, массировал тело, фыркал и делал все это как-то преувеличенно, как будто хотел вытравить из себя эту жуткую, внушающую подозрения неуверенность, охватившую станцию.
В шкафу я нашел легкий тренировочный костюм, который можно было носить под скафандром, переложил в карман все свое скромное имущество. Между листами блокнота я нащупал что-то твердое это был каким-то чудом попавший сюда ключ от моего земного жилья. Я повертел его в руках, не зная, что с ним делать, потом положил на стол. Мне пришло в голову, что неплохо бы иметь какое-нибудь оружие. Универсальный перочинный нож тут явно не годился, но ничего другого у меня не было, а я еще не дошел до такого состояния, чтобы искать ядерный излучатель или что-нибудь в этом роде. Я уселся на металлический стульчик, который стоял посредине пустого пространства, в отдалении от всех вещей. Мне хотелось побыть одному. С удовольствием я отметил, что у меня есть еще полчаса времени. Стрелки на двадцатичетырехчасовом циферблате показывали семь. Солнце заходило. Семь часов местного времени значит двадцать часов на борту «Прометея». На экранах Моддарда Солярис, наверно, уже уменьшился до размеров искорки и ничем не отличался от звезд. Но какое я имею отношение к «Прометею»? Я закрыл глаза. Стояла полная тишина, только в ванной капли воды глухо стучали по кафелю.
Гибарян мертв. Если я правильно понял Снаута, с момента его смерти прошло всего несколько часов.
Что сделали с его телом? Похоронили? Правда, здесь, на Солярисе, этого сделать нельзя. Некоторое время я обдумывал это, будто судьба мертвого была так уж важна. Поняв бессмысленность подобных размышлений, я встал и начал ходить по комнате, поддавая носком беспорядочно разбросанные книги. Потом поднял с пола фляжку из темного стекла, такую легкую, будто она была сделана из бумаги. Посмотрел сквозь нее в окно, в мрачно пламенеющие, затянутые грозным туманом последние лучи заката. Что со мной? Почему я занимаюсь какими-то глупостями, какой-то ненужной ерундой?
Я вздрогнул зажегся свет. Очевидно, фотоэлементы среагировали на наступающие сумерки. Я был полон ожидания, напряжение нарастало до такой степени, что мне уже действовало на нервы пустое пространство за спиной. С этим пора было кончать.
Я придвинул кресло к полкам, взял хорошо известный мне второй том старой монографии Хьюджеса и Эгла «История Соляриса» и начал его перелистывать, подперев толстый жесткий переплет коленом.
Солярис был открыт почти за сто лет до того, как я родился. Планета обращается вокруг двух солнц красного и голубого. В течение сорока с лишним лет к ней не приближался ни один космический корабль. В то время теория Гамова Шепли о невозможности зарождения жизни на планетах двойных звезд не вызывала сомнений. Орбиты таких планет непрерывно изменяются из-за непостоянства сил притяжения, вызванного взаимным обращением двух солнц.
Возникающие изменения гравитационного поля сокращают или растягивают орбиту планеты, и зародыши жизни, если они возникнут, будут уничтожены испепеляющим жаром или космическим холодом. Эти изменения происходят регулярно через каждые несколько миллионов лет, то есть в астрономическом или биологическом масштабе за очень короткий промежуток времени, так как эволюция требует сотен миллионов, если не миллиардов лет.
Солярис, по предварительным подсчетам, должен был за пятьсот тысяч лет приблизиться на расстояние половины астрономической единицы к своему красному солнцу,[1] а еще через миллион лет упасть в его раскаленную бездну. Но уже через несколько лет выяснилось, что орбита планеты не подвергается ожидаемым изменениям, вроде бы она постоянная, такая же постоянная, как орбиты планет нашей Солнечной системы.
Повторенные на этот раз с максимальной точностью наблюдения и вычисления лишь подтвердили то, что уже было известно: орбита Соляриса нестабильна. И если до этого Солярис был всего-навсего одной из нескольких сотен ежегодно открываемых планет, которым в статистических сборниках уделяют десяток строчек, где описываются элементы их движения, то теперь он немедленно перешел в ранг небесного тела, достойного самого пристального внимания.
Через четыре года после этого открытия планету облетела экспедиция Оттеншельда, который изучал Солярис с «Лаокоона» и двух вспомогательных космолетов.[2] Эта экспедиция носила характер предварительной разведки, тем более что высадиться на планету она не могла. Ученые запустили на экваториальные и полярные орбиты большое количество автоматических спутников-наблюдателей. Спутники должны были главным образом измерять гравитационные потенциалы. Кроме того, изучался океан, почти целиком покрывающий планету, и немногочисленные возвышающиеся над его поверхностью плоскогорья. Их общая площадь оказалась меньше, чем территория Европы, хотя Солярис имел диаметр на двадцать процентов больше земного. Эти лоскутки скалистой пустынной суши, разбросанные как попало, скопились главным образом в Южном полушарии. Был также определен состав атмосферы, лишенной кислорода, и произведены чрезвычайно точные измерения плотности планеты, альбедо и других астрономических показателей.[3] Как и ожидалось, ни на жалких клочках суши, ни в океане не удалось обнаружить никаких следов жизни.
В течение дальнейших десяти лет Солярис, теперь уже находящийся в центре внимания всех наблюдателей этого района, демонстрировал поразительную тенденцию к сохранению своей, вне всякого сомнения, гравитационно-нестабильной орбиты. Запахло было скандалом, так как вину за такие результаты наблюдений пытались возложить (заботясь о благе науки) то на определенных людей, то на вычислительные машины, которыми они пользовались.
Отсутствие средств задержало отправку специальной соляристической экспедиции еще на три года, вплоть до того момента, когда Шеннон, укомплектовавший команду, получил от института три космических корабля тоннажа «С» космодромного класса. За полтора года до прибытия экспедиции, которая вылетела с альфы Водолея, другая исследовательская группа по поручению института вывела на околосоляристическую орбиту автоматический сателлоид Луну-247. Этот сателлоид после трех последовательных реконструкций, отделенных друг от друга десятками лет, работает до сегодняшнего дня. Данные, которые он собрал, окончательно подтвердили выводы экспедиции Оттеншельда об активном характере движения океана.
Один корабль Шеннона остался на дальней орбите, два других после предварительных приготовлений сели у Южного полюса планеты на скалистом клочке суши, который занимает около тысячи квадратных километров. Работа экспедиции закончилась через восемнадцать месяцев и прошла очень успешно, за исключением одного несчастного случая, вызванного неисправностью аппаратуры. Однако ученые экспедиции раскололись на два враждующих лагеря. Предметом спора стал океан. На основании анализов он был признан органическим образованием (назвать его живым никто еще не решался). Но если биологи видели в нем организм весьма примитивный, что-то вроде одной чудовищно разросшейся жидкой клетки (они называли ее «добиологическая формация»), которая окружила всю планету студенистой оболочкой, местами глубиной в несколько километров, то астрономы и физики утверждали, что это должна быть чрезвычайно высокоорганизованная структура, сложностью своего строения превосходящая земные организмы, коль скоро она в состоянии активно влиять на форму планетной орбиты. Никакой иной причины, объясняющей стабилизацию Соляриса, открыто не было. Кроме того, планетофизики установили связь между определенными процессами, происходящими в плазменном океане, и локальными колебаниями гравитационного потенциала, которые зависели от океанического «обмена веществ».
Таким образом, физики, а не биологи выдвинули парадоксальную формулировку «плазматическая машина», имея в виду образование, в нашем понимании, возможно, и неодушевленное, но способное к целенаправленным действиям в астрономическом масштабе.
В этом споре, который за несколько недель втянул в свою орбиту все выдающиеся авторитеты, доктрина Гамова Шепли пошатнулась впервые за восемьдесят лет.
Некоторое время ее еще пытались защищать, утверждая, что океан ничего общего с жизнью не имеет, что он является даже не образованием пара или добиологическим, а всего лишь геологической формацией, по всей вероятности необычной, но способной лишь к стабилизации орбиты Соляриса посредством изменения силы тяжести; при этом ссылались на закон Ле Шателье.
TEXT,
'UTF-8'
),
$matches
);
// Инициализация цепочки
$chain = new chain(1);
// Обучение
$chain->learn($matches[0]);
// Инициализация генератора
$generator = new generator(new rng(222, 666), $chain);
// Генерация
$result = $generator->generate();
// Обрезка результата
for ($password = [], $choose = rand(0, count($result) - 20); isset($result[$choose]) && count($password) < $length; $choose++) $password[] = $result[$choose];
return implode(' ', $password);
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Сгенерировать классический пароль
*
* @param int $length Длина (количество символов)
* @param array &$errors Реестр ошибок
*
* @return ?string Пароль
*/
public static function classic(int $length = 12, array &$errors = []): ?string
{
try {
// Инициализация реестра символов
$symbols = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
// Инициализация буфера пароля
$password = '';
// Определение максимальной длины
$max = mb_strlen($symbols, '8bit') - 1;
if ($max < 1) throw new Exception('Длина пароля должна быть не менее двух символов');
// Генерация пароля
for ($i = 0; $i < $length; ++$i) $password .= $symbols[random_int(0, $max)];
return $password;
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}

View File

@ -1,89 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\account;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Встроенные библиотеки
use exception;
/**
* Модель приглашения
*
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class invite extends core
{
/**
* Коллекция
*/
public const COLLECTION = 'invite';
/**
* Инстанция документа приглашения в базе данных
*/
public ?_document $document;
/**
* Прочитать
*
* @param string $invite Ключ приглашения
* @param array &$errors Реестр ошибок
*
* @return ?self Инстанция приглашения, если оно найдено
*/
public static function read(string $invite, array &$errors = []): ?self
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Инициализация инстанции приглашения
$instance = new self;
// Поиск приглашения
$instance->document = collection::search(
static::$db->session,
sprintf(
<<<AQL
FOR d IN %s
FILTER d.key == '%s' && d.active == true
RETURN d
AQL,
self::COLLECTION,
$invite
)
);
if ($instance->document instanceof _document) return $instance;
else throw new exception('Не удалось найти инстанцию приглашения в базе данных');
} throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
public function from(): ?account
{
return null;
}
}

View File

@ -1,352 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\account;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Встроенные библиотеки
use exception;
/**
* Модель сессий
*
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class session extends core
{
/**
* Коллекция
*/
public const COLLECTION = 'session';
/**
* Инстанция документа сессии в базе данных
*/
public _document $document;
/**
* Конструктор
*
* Инициализация сессии и запись в свойство $this->document
*
* @param ?string $hash Хеш сессии в базе данных
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
* @param array &$errors Реестр ошибок
*
* @return static Инстанция сессии
*/
public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
{
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 && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
time()
))) {
// Найдена сессия по хешу
// Запись в свойство
$this->document = $session;
} else if ($session = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d.ip == '%s' && d.expires > %d && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
$_SERVER['REMOTE_ADDR'],
time()
))) {
// Найдена сессия по данным пользователя
// Запись в свойство
$this->document = $session;
} else {
// Не найдена сессия
// Запись сессии в базу данных
$_id = document::write(static::$db->session, self::COLLECTION, [
'status' => 'active',
'expires' => $expires ?? time() + 604800,
'ip' => $_SERVER['REMOTE_ADDR']
]);
if ($session = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d._id == '$_id' && d.expires > %d && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
time()
))) {
// Найдена только что созданная сессия
// Запись хеша
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
if (document::update(static::$db->session, $session)) {
// Записано обновление
// Запись в свойство
$this->document = $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()
];
}
}
public function __destruct()
{
// Закрыть сессию
}
/**
* Инициализировать связб сессии с аккаунтом
*
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
*
* @param account $account Инстанция аккаунта
* @param array &$errors Реестр ошибок
*
* @return bool Связан аккаунт?
*/
public function connect(account $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 (
collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
FILTER document._from == '%s' && document._to == '%s'
LIMIT 1
RETURN document
AQL,
self::COLLECTION . '_edge_' . account::COLLECTION,
$this->document->getId(),
$account->getId()
)) instanceof _document
|| document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
'_from' => $this->document->getId(),
'_to' => $account->getId()
])
) {
// Найдено, либо создано ребро: session -> account
return true;
} else throw new exception('Не удалось создать ребро: session -> account');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Найти связанный аккаунт
*
* @param array &$errors Реестр ошибок
*
* @return ?account Инстанция аккаунта, если удалось найти
*/
public function account(array &$errors = []): ?account
{
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)
) {
// Инициализированы коллекции
// Инициализация инстанции аккаунта
$account = new account;
// Поиск инстанции аккаунта в базе данных
$account->document = collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
LET edge = (
FOR edge IN %s
FILTER edge._from == '%s'
SORT edge._key DESC
LIMIT 1
RETURN edge
)
FILTER document._id == edge[0]._to
LIMIT 1
RETURN document
AQL,
account::COLLECTION,
self::COLLECTION . '_edge_' . account::COLLECTION,
$this->getId()
));
if ($account->document instanceof _document) return $account;
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Записать в буфер сессии
*
* @param array $data Данные для записи
* @param array &$errors Реестр ошибок
*
* @return bool Записаны данные в буфер сессии?
*/
public function write(array $data, array &$errors = []): bool
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Проверка инициализированности инстанции документа из базы данных
if (!isset($this->document)) throw new exception('Не инициализирована инстанция документа из базы данных');
// Запись параметров в инстанцию документа из базы данных
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
if (document::update(static::$db->session, $this->document)) {
// Записано обновление
return true;
}
throw new exception('Не удалось записать данные в буфер сессии');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Записать
*
* Записывает свойство в инстанцию документа сессии из базы данных
*
* @param string $name Название
* @param mixed $value Содержимое
*
* @return void
*/
public function __set(string $name, mixed $value = null): void
{
$this->document->{$name} = $value;
}
/**
* Прочитать
*
* Читает свойство из инстанции документа сессии из базы данных
*
* @param string $name Название
*
* @return mixed Данные свойства инстанции сессии или инстанции документа сессии из базы данных
*/
public function __get(string $name): mixed
{
return $this->document->{$name};
}
/**
* Проверить инициализированность
*
* Проверяет инициализированность свойства в инстанции документа сессии из базы данных
*
* @param string $name Название
*
* @return bool Свойство инициализировано?
*/
public function __isset(string $name): bool
{
return isset($this->document->{$name});
}
/**
* Удалить
*
* Деинициализировать свойство в инстанции документа сессии из базы данных
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
unset($this->document->{$name});
}
/**
* Выполнить метод
*
* Выполнить метод в инстанции документа сессии из базы данных
*
* @param string $name Название
* @param array $arguments Аргументы
*/
public function __call(string $name, array $arguments = [])
{
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
}
}

View 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;
}
}

View 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;
}
}

View File

@ -1,268 +1,54 @@
@keyframes glare {
2%,
100% {
left : 130%;
bottom : -200%;
width : 120px;
opacity: 0.7;
}
}
main {
z-index: 1000;
top: 20%;
position: relative;
height: unset;
display: flex;
flex-direction: unset;
justify-content: center;
align-items: unset;
}
div.column {
#authentication {
width : 25vw;
margin : 25vh auto;
padding : 70px 40px;
display : flex;
flex-direction : column;
gap: 20px;
background-color: var(--background-light-1);
}
section.panel {
--display : flex;
z-index : 1000;
width : 400px;
position : absolute;
display : flex;
flex-direction: column;
}
div.column>section.panel {
position : unset;
}
section.panel.medium {
width: 300px;
}
section.panel.small {
width: 220px;
}
section.panel#mnemonic {
margin-left: -570px;
}
section.panel#classic {
margin-left: 570px;
}
section.panel>section.body>ul {
margin: 0 5%;
padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
list-style: square;
}
section.panel>section.body>ul>li {
font-size: 0.8rem;
word-break: break-word;
animation-duration : .35s;
animation-name : uprise;
animation-fill-mode : forwards;
animation-timing-function: cubic-bezier(.47,0,.74,.71);
}
section.panel>section.body>dl {
margin: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
section.panel>section.body>dl>* {
word-break: break-word;
animation-duration : .35s;
animation-name : uprise;
animation-fill-mode : forwards;
animation-timing-function: cubic-bezier(.47,0,.74,.71);
}
section.panel>section.body>dl>dt {
margin-left: 20px;
display: none;
font-size: 0.9rem;
font-weight: bold;
}
section.panel>section.body>dl>dd {
margin-left: unset;
font-size: 0.8rem;
}
section.panel>section.header {
z-index : 1000;
height : 50px;
display : flex;
justify-content: center;
align-items: end;
animation-duration: 120s;
border-radius : 3px 3px 0 0;
background-color : var(--background-above);
}
section#profile>section.header {
margin-left : -50px;
#authentication>section.header {
width : 100px;
height : 100px;
padding : 30px 0;
clip-path : url(#profile-header-mask);
margin : auto;
display : flex;
border-radius: 100%;
overflow : hidden;
}
section#profile>section.header>img.avatar {
z-index : 1500;
left : 6px;
top : 36px;
width : 88px;
height : 88px;
position : absolute;
#authentication>section {
margin: unset;
color : #fff;
}
#authentication>section.header>img {
width : 90%;
height : 90%;
margin : auto;
object-fit : cover;
border-radius : 100%;
cursor : pointer;
image-rendering : smooth;
box-shadow : 0px 0px 12px 0px rgba(0, 0, 0, 0.5);
-webkit-box-shadow: 0px 0px 12px 0px rgba(0, 0, 0, 0.5);
-moz-box-shadow : 0px 0px 12px 0px rgba(0, 0, 0, 0.5);
box-shadow : 1px 0px 12px 0px rgba(0, 0, 0, 1);
-webkit-box-shadow: 1px 0px 12px 0px rgba(0, 0, 0, 1);
-moz-box-shadow : 1px 0px 12px 0px rgba(0, 0, 0, 1);
}
section#profile>section.header>img.avatar:hover {
left : 0;
top : 30px;
width : 100px;
height : 100px;
box-shadow : 0px 0px 8px 0px rgba(0, 0, 0, 0.3);
-webkit-box-shadow: 0px 0px 8px 0px rgba(0, 0, 0, 0.3);
-moz-box-shadow : 0px 0px 8px 0px rgba(0, 0, 0, 0.3);
#authentication>:is(b, a) {
margin-left: 15%;
}
section#profile>section.header>img.cover {
z-index : -5000;
left : -50px;
top : 0;
position : absolute;
width : calc(100% + 100px);
height : 100%;
object-position: 0px 30%;
object-fit : cover;
clip-path : polygon(50px 0, calc(100% - 50px) 0, calc(100% - 50px) 100%, 50px 100%);
border-radius : 0 0 3px 3px;
background : var(--background-above);
#authentication>b {
margin-top: 30px;
font-size : 1.2em;
}
section#profile>section.header>div.glare {
z-index : 3000;
left : -30px;
top : -300px;
width : 30px;
height : 400%;
position : absolute;
rotate : 25deg;
opacity : 0.2;
filter : unset;
pointer-events : none;
animation-name : glare;
animation-duration : 32s;
animation-delay : 2s;
animation-fill-mode : forwards;
animation-timing-function: linear;
background-color : #fff;
#authentication>ul {
margin-left: 25%;
}
section#profile>section.header>div {
animation-duration: 80s;
}
section#profile>section.header>a {
margin : auto;
width : 100%;
margin-left : 110px;
padding-bottom: 0.5ex;
white-space : nowrap;
overflow-x : hidden;
text-overflow : ellipsis;
font-size : 1.3em;
font-weight : bold;
color : var(--text-inverse);
text-shadow : 0 0 8px #00000080;
}
section.panel>section.header>:is(h1, h2, h3) {
margin-bottom: unset;
}
section.panel>section.body {
padding : 20px 30px;
gap : 10px;
display : flex;
flex-direction : column;
border-radius : 0 0 3px 3px;
background-color : var(--background-above);
}
section#profile>section.body>ul {
margin : unset;
margin-left : 10%;
}
section#profile>section.body ul ul {
#authentication ul ul {
padding-top: 1ex;
}
section#profile>section.body ul li:not(:last-child) {
#authentication ul li:not(:last-child) {
margin-bottom: 1ex;
}
section#profile>section.body div.buttons {
margin-top: 10px;
display: flex;
}
section#profile>section.body div.buttons>button {
padding : 1ex 2ex;
cursor : pointer;
border-radius : 3px;
font-size : 0.9em;
background-color: unset;
}
section#profile>section.body div.buttons>button:hover {
color: var(--text-hover);
}
section#profile>section.body div.buttons>button:active {
color : var(--text-active);
transition: unset;
}
section#profile>section.body div.buttons>button:first-of-type {
margin-left : auto;
margin-right: 5%;
}
section#profile>section.body div.buttons>button:last-of-type {
margin-right: auto;
}
section#profile>section.body div.buttons>button.accept {
padding : 1ex 5ex;
color : var(--text-inverse);
background-color: #63954d;
}
section#profile>section.body div.buttons>button.accept:hover {
color : var(--text-inverse-above);
background-color: #6fa259;
}
section#profile>section.body div.buttons>button.accept:active {
background-color: #63954d;
}

View File

@ -1,64 +0,0 @@
@keyframes input-error {
0%,
20% {
background-color: var(--input-error);
}
50%,
100% {
background-color: var(--background-above-1);
}
}
@keyframes uprise {
0% {
opacity: 0;
filter: blur(2px);
}
100% {
opacity: 1;
filter: blur(0px);
}
}
@keyframes window-vertical-open {
0% {
height: 0;
opacity: 0;
}
100% {
height: var(--height, inherit);
opacity: var(--opacity, 1);
}
}
@keyframes window-vertical-close {
0% {
height: var(--height, inherit);
opacity: var(--opacity, 1);
}
100% {
height: 0;
opacity: 0;
}
}
.animation.window:not(.hidden, .horizontal) {
overflow: hidden;
animation-duration : .1s;
animation-name : window-vertical-open;
animation-fill-mode : forwards;
animation-timing-function: ease-in;
}
.animation.window.hidden:not(.horizontal) {
overflow: hidden;
animation-duration : .05s;
animation-name : window-vertical-close;
animation-fill-mode : forwards;
animation-timing-function: ease-out;
}

View File

@ -1,139 +0,0 @@
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Hair.woff2') format('woff2'), url('/fonts/fira/FiraSans-Hair.woff') format('woff');
font-weight: 100;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-HairItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-HairItalic.woff') format('woff');
font-weight: 100;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-UltraLight.woff2') format('woff2'), url('/fonts/fira/FiraSans-UltraLight.woff') format('woff');
font-weight: 200;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-UltraLightItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-UltraLightItalic.woff') format('woff');
font-weight: 200;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Light.woff2') format('woff2'), url('/fonts/fira/FiraSans-Light.woff') format('woff');
font-weight: 300;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-LightItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-LightItalic.woff') format('woff');
font-weight: 300;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Regular.woff2') format('woff2'), url('/fonts/fira/FiraSans-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Italic.woff2') format('woff2'), url('/fonts/fira/FiraSans-Italic.woff') format('woff');
font-weight: 400;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraMono-Medium.woff2') format('woff2'), url('/fonts/fira/FiraMono-Medium.woff') format('woff');
font-weight: 500;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-MediumItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-MediumItalic.woff') format('woff');
font-weight: 500;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-SemiBold.woff2') format('woff2'), url('/fonts/fira/FiraSans-SemiBold.woff') format('woff');
font-weight: 600;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-SemiBoldItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-SemiBoldItalic.woff') format('woff');
font-weight: 600;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Bold.woff2') format('woff2'), url('/fonts/fira/FiraSans-Bold.woff') format('woff');
font-weight: 700;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-BoldItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-BoldItalic.woff') format('woff');
font-weight: 700;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-ExtraBold.woff2') format('woff2'), url('/fonts/fira/FiraSans-ExtraBold.woff') format('woff');
font-weight: 800;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-ExtraBoldItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-ExtraBoldItalic.woff') format('woff');
font-weight: 800;
font-style: italic;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-Heavy.woff2') format('woff2'), url('/fonts/fira/FiraSans-Heavy.woff') format('woff');
font-weight: 900;
font-style: normal;
}
@font-face {
font-family: Fira;
src: url('/fonts/fira/FiraSans-HeavyItalic.woff2') format('woff2'), url('/fonts/fira/FiraSans-HeavyItalic.woff') format('woff');
font-weight: 900;
font-style: italic;
}
@font-face {
font-family: Fira Mono;
src: url('/fonts/fira/FiraMono-Regular.woff2') format('woff2'), url('/fonts/fira/FiraMono-Regular.woff') format('woff');
font-weight: 400;
font-style: normal;
}
@font-face {
font-family: Fira Mono;
src: url('/fonts/fira/FiraMono-Bold.woff2') format('woff2'), url('/fonts/fira/FiraMono-Bold.woff') format('woff');
font-weight: 600;
font-style: normal;
}

View File

@ -1,63 +0,0 @@
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-regular.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-regular.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-bold.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-bold.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-italic.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-italic.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-bolditalic.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-bolditalic.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-regular-subset.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-regular-subset.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-bold-subset.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-bold-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-italic-subset.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-italic-subset.woff?sha=3114f1256') format('woff');
font-weight: 400;
font-style: italic;
font-display: swap;
}
@font-face {
font-family: 'Hack';
src: url('/fonts/hack/hack-bolditalic-subset.woff2?sha=3114f1256') format('woff2'), url('/fonts/hack/hack-bolditalic-subset.woff?sha=3114f1256') format('woff');
font-weight: 700;
font-style: italic;
font-display: swap;
}

View File

@ -1,4 +1,9 @@
@keyframes red {
from {
left: -250%;
top : -300%;
}
25% {
left: -240%;
top : -130%;
@ -14,13 +19,18 @@
top : -230%;
}
100% {
to {
left: -250%;
top : -300%;
}
}
@keyframes blue {
from {
left: -280%;
top : -80%;
}
25% {
left: -180%;
top : -100%;
@ -36,13 +46,18 @@
top : -300%;
}
100% {
to {
left: -280%;
top : -80%;
}
}
@keyframes green {
from {
left: -180%;
top : -100%;
}
25% {
left: -120%;
top : -250%;
@ -58,13 +73,17 @@
top : -80%;
}
100% {
to {
left: -180%;
top : -100%;
}
}
@keyframes wrap-background {
@keyframes background {
from {
background-color: #ff5c5c;
}
25% {
background-color: #9395ff;
}
@ -77,56 +96,47 @@
background-color: #534eff;
}
100% {
to {
background-color: #ff5c5c;
}
}
:is(div, section).gradient {
#authentication>section.header.gradient {
position : relative;
overflow : hidden;
animation-duration : 30s;
animation-name : wrap-background;
animation-name : background;
animation-iteration-count: infinite;
background-repeat : no-repeat;
animation-timing-function: ease-in-out;
background-color : #ff5c5c;
}
:is(div, section).gradient>div:not(.gradient) {
#authentication>section.header.gradient>div {
z-index : -1000;
width : 500%;
height : 500%;
position : absolute;
pointer-events : none;
filter : blur(200px);
animation-duration : 12s;
background-repeat : no-repeat;
animation-timing-function: ease-in-out;
animation-iteration-count: infinite;
}
:is(div, section).gradient>div.red {
left : -250%;
top : -300%;
#authentication>section.header.gradient>div.red {
animation-name: red;
background-image: radial-gradient(circle,
rgba(255, 25, 25, 1) 0%,
rgba(0, 0, 0, 0) 35%);
}
:is(div, section).gradient>div.blue {
left : -280%;
top : -80%;
#authentication>section.header.gradient>div.blue {
animation-name: blue;
background-image: radial-gradient(circle,
rgba(25, 25, 255, 0.6) 0%,
rgba(0, 0, 0, 0) 35%);
}
:is(div, section).gradient>div.green {
left : -180%;
top : -100%;
#authentication>section.header.gradient>div.green {
animation-name: green;
background-image: radial-gradient(circle,
rgba(25, 255, 25, 1) 0%,

View File

@ -1,245 +0,0 @@
@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 {
z-index : 0;
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);
}

View File

@ -1,31 +0,0 @@
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;
}

View File

@ -1,29 +0,0 @@
.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)
}

View File

@ -1,33 +0,0 @@
i.arrow.right {
box-sizing: border-box;
position: relative;
display: block;
width: 22px;
--height: 22px;
height: var(--height);
}
i.arrow.right::after,
i.arrow.right::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
right: 3px;
}
i.arrow.right::after {
width: 8px;
height: 8px;
border-top: 2px solid;
border-right: 2px solid;
transform: rotate(45deg);
bottom: 7px;
}
i.arrow.right::before {
width: 16px;
height: 2px;
bottom: 10px;
background: currentColor;
}

View File

@ -1,48 +0,0 @@
i.keyhole,
i.keyhole::after,
i.keyhole::before {
display: block;
box-sizing: border-box;
border-radius: 20px;
}
i.keyhole {
--width: 20px;
--height: 20px;
position: relative;
width: 20px;
height: 20px;
border: 2px solid;
}
i.keyhole::after,
i.keyhole::before {
position: absolute;
content: '';
}
i.keyhole::before {
left: 5px;
top: 3px;
width: 6px;
height: 6px;
border: 2px solid;
}
i.keyhole::after {
left: 7px;
bottom: 3px;
width: 2px;
height: 5px;
background: currentColor;
}
label>i.keyhole:first-child {
left: 7px;
scale: .9;
border: 2.1px solid;
}
i.keyhole+input {
padding-left: 34px;
}

View File

@ -1,28 +0,0 @@
i.lock {
--width: 12px;
--height: 11px;
position: relative;
margin-top: -12px;
width: 12px;
height: 11px;
display: block;
box-sizing: border-box;
border: 2px solid;
border-top-right-radius: 50%;
border-top-left-radius: 50%;
border-bottom: transparent;
}
i.lock::after {
left: -4px;
top: 9px;
position: absolute;
width: 16px;
height: 10px;
display: block;
box-sizing: border-box;
content: '';
box-shadow: 0 0 0 2px;
border-radius: 2px;
border: 2px solid transparent;
}

View File

@ -1,30 +0,0 @@
i.mail,
i.mail::after {
--width: 18px;
--height: 14px;
height: 14px;
display: block;
box-sizing: border-box;
border: 2px solid;
}
i.mail {
position: relative;
width: 18px;
overflow: hidden;
border-radius: 2px;
}
i.mail::after {
position: absolute;
left: 0;
bottom: 3px;
width: 14px;
transform: rotate(-45deg);
content: "";
border-radius: 3px;
}
i.mail+input {
padding-left: 36px;
}

View File

@ -1,34 +0,0 @@
i.nametag {
--width: 6px;
--height: 6px;
box-sizing: border-box;
position: relative;
width: 6px;
height: 6px;
display: block;
border: 2px solid;
}
i.nametag::before {
left: -5px;
top: -5px;
position: absolute;
width: 12px;
height: 12px;
display: block;
box-sizing: border-box;
content: '';
box-shadow:
-5px -5px 0 -3px,
5px 5px 0 -3px,
5px -5px 0 -3px,
-5px 5px 0 -3px;
}
label>i.nametag:first-child {
left: 13px;
}
i.nametag+input {
padding-left: 32px;
}

View File

@ -1,53 +0,0 @@
i.user.add {
--width: 20px;
--height: 18px;
width: 20px;
height: 18px;
display: block;
box-sizing: border-box;
background:
linear-gradient(
to left,
currentColor 8px,
transparent 0)
no-repeat 14px 6px/6px 2px,
linear-gradient(
to left,
currentColor 8px,
transparent 0)
no-repeat 16px 4px/2px 6px;
}
i.user.add::after,
i.user.add::before {
content: '';
position: absolute;
display: block;
box-sizing: border-box;
border: 2px solid
}
i.user.add::before {
left: 2px;
top: 0;
width: 8px;
height: 8px;
border-radius: 30px;
}
i.user.add::after {
top: 9px;
width: 12px;
height: 9px;
border-bottom: 0;
border-top-left-radius: 3px;
border-top-right-radius: 3px;
}
label>i.user.add:first-child {
left: 9px;
}
i.user.add+input {
padding-left: 37px;
}

View File

@ -1,9 +1,8 @@
@import url('/fonts/comissioner.ttf');
@media (prefers-color-scheme: light) {
:root {
--background-above-1 : #fff;
--background-above : #fff6f6;
--background : #e8dada;
--background-below : #d7c5c5;
--background : #eee;
--background-inverse : #221e1e;
--background-inverse-dark : #120f0f;
--node-background-important: #c3eac3;
@ -11,63 +10,54 @@
--node-background : #bdb;
--connection : #b2b7b2;
--connection-completed : #d1d1d1;
--text : #151313;
--text-hover : #463e3e;
--text-active : #0e0e0e;
--text-inverse-above : #fff;
--text-inverse : #efefef;
--text-inverse-below : #d0d0d0;
--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-above-1: #322d2d;
--background-above : #2b2525;
--background-light-3: #403939;
--background-light-2: #322d2d;
--background-light-1: #2b2525;
--background-light : #252020;
--background : #221e1e;
--background-below : #121010;
--block-background : ;
--node-background : #221e1e;
--text : #e6e6e6;
--text-hover : #fff;
--text-active : #d0d0d0;
--text-inverse : #020202;
--text-inverse : 'dark';
--red-light-1 : #dc4343;
--red-light : #bf3737;
--red : #a43333;
--red-dark : #8d2a2a;
--input-error : #6c2424;
}
}
@keyframes page-background-gradient {
25% {
left: -350%;
top : 0%;
}
50% {
left: 0%;
top : 0%;
}
75% {
left: 0%;
top : -350%;
}
to {
left: -350%;
top : -350%;
}
}
:root {
--link : #3c76ff;
--link-hover : #6594ff;
--link-active: #3064dd;
--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 {
@ -79,27 +69,6 @@
user-select : none;
}
.hidden:not(.animation) {
display: none !important;
}
* {
text-decoration: none;
outline : none;
border : none;
color : var(--text);
font-family : Fira, sans-serif;
transition : 0.1s ease-out;
}
pre, code {
font-family: Hack, monospace;
}
button {
cursor: pointer;
}
a {
color: var(--link);
}
@ -110,82 +79,15 @@ a:hover {
a:active {
color: var(--link-active);
transition: unset;
}
label {
position: relative;
height: 26px;
display: flex;
overflow: hidden;
border-radius: 2px;
}
label>i:first-child {
left: 8px;
top: calc((26px - var(--height)) / 2);
position: absolute !important;
margin: auto;
color: #8c7d7d;
}
label * {
/* color: var(--text-inverse); */
}
label>input {
width: 100%;
padding: 0 8px;
background-color: var(--background-above-1);
}
label>input+button {
background-color: var(--red);
}
i+input {
padding-left: 30px;
}
input.error {
animation-duration : 1s;
animation-name : input-error;
animation-fill-mode : forwards;
animation-timing-function: ease-in;
}
section.header>h1 {
font-size: 1.3rem;
line-height: 1.3rem;
}
section.header>:is(h2, h3) {
font-size: 1.1rem;
line-height: 1.1rem;
}
body {
height : 100vh;
margin : 0;
overflow : hidden;
background-color: var(--background);
}
body>div.background {
z-index : -50000;
left : -350%;
top : -350%;
width : 500%;
height : 500%;
position : absolute;
filter : blur(200px);
animation-duration : 15s;
animation-name : page-background-gradient;
animation-iteration-count: infinite;
background-repeat : no-repeat;
animation-timing-function: linear;
background-image : radial-gradient(circle, var(--background-above) 0%, rgba(0, 0, 0, 0) 100%);
}
aside {
z-index : 500;
grid-column: 1/ 4;
@ -278,8 +180,6 @@ main {
height: 100%;
display: flex;
flex-direction: column;
justify-content : center;
align-items : center;
}
footer {

Some files were not shown because too many files have changed in this diff Show More