большая разработка аутентификации

This commit is contained in:
2023-03-02 11:46:14 +10:00
parent 0aae5b6558
commit 687db4e4b1
129 changed files with 2282 additions and 646 deletions

View File

@@ -0,0 +1,222 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\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 extends core
{
/**
* Коллекция
*/
public const COLLECTION = 'account';
/**
* Инстанция в базе данных
*/
public ?_document $instance;
/**
* Прочитать
*
* @param string $login Входной псевдоним
* @param array &$errors Журнал ошибок
*
* @return ?self Инстанция аккаунта, если найден
*/
public static function read(string $login, array &$errors = []): ?self
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Инициализация инстанции аккаунта
$instance = new self;
// Поиск аккаунта
$instance->instance = collection::search(
static::$db->session,
sprintf(
<<<AQL
FOR d IN %s
FILTER d.login == '%s'
RETURN d
AQL,
self::COLLECTION,
$login
)
);
return $instance;
}
throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Создать
*
* @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

@@ -1,169 +0,0 @@
<?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

@@ -18,126 +18,131 @@ use exception;
*/
class core extends model
{
/**
* Коллекция в которой хранятся аккаунты
*/
public const SETTINGS = '../settings/arangodb.php';
/**
* Коллекция в которой хранятся аккаунты
*/
public const SETTINGS = '../settings/arangodb.php';
/**
* Соединение с базой данных
*/
protected static connection $db;
/**
* Постфикс
*/
public string $postfix = '';
public function __construct(connection $db = null)
{
if (isset($db)) {
// Получена инстанция соединения с базой данных
/**
* Соединение с базой данных
*/
protected static connection $db;
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
public function __construct(connection $db = null)
{
if (isset($db)) {
// Получена инстанция соединения с базой данных
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
} else {
// Не получена инстанция соединения с базой данных
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
}
}
/**
* Записать свойство
*
* @param string $name Название
* @param mixed $value Значение
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'db' => (function () use ($value) {
if ($this->__isset('db')) {
// Свойство уже было инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
} else {
// Не получена инстанция соединения с базой данных
// Свойство ещё не было инициализировано
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
if ($value instanceof connection) {
// Передано подходящее значение
// Запись свойства (успех)
self::$db = $value;
} else {
// Передано неподходящее значение
// Выброс исключения (неудача)
throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией mirzaev\arangodb\connection', 500);
}
}
}
})(),
default => parent::__set($name, $value)
};
}
/**
* Записать свойство
*
* @param string $name Название
* @param mixed $value Значение
*/
public function __set(string $name, mixed $value = null): void
{
match ($name) {
'db' => (function () use ($value) {
if ($this->__isset('db')) {
// Свойство уже было инициализировано
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'db' => (function () {
if (!$this->__isset('db')) {
// Свойство не инициализировано
// Выброс исключения (неудача)
throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
} else {
// Свойство ещё не было инициализировано
// Инициализация значения по умолчанию исходя из настроек
$this->__set('db', new connection(require static::SETTINGS));
}
if ($value instanceof connection) {
// Передано подходящее значение
return self::$db;
})(),
default => parent::__get($name)
};
}
// Запись свойства (успех)
self::$db = $value;
} else {
// Передано неподходящее значение
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => parent::__isset($name)
};
}
// Выброс исключения (неудача)
throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией mirzaev\arangodb\connection', 500);
}
}
})(),
default => parent::__set($name, $value)
};
}
/**
* Прочитать свойство
*
* @param string $name Название
*
* @return mixed Содержимое
*/
public function __get(string $name): mixed
{
return match ($name) {
'db' => (function () {
if (!$this->__isset('db')) {
// Свойство не инициализировано
// Инициализация значения по умолчанию исходя из настроек
$this->__set('db', new connection(require static::SETTINGS));
}
return self::$db;
})(),
default => parent::__get($name)
};
}
/**
* Проверить свойство на инициализированность
*
* @param string $name Название
*/
public function __isset(string $name): bool
{
return match ($name) {
default => parent::__isset($name)
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => parent::__isset($name)
};
}
/**
* Удалить свойство
*
* @param string $name Название
*/
public function __unset(string $name): void
{
match ($name) {
default => parent::__isset($name)
};
}
/**
* Статический вызов
*
* @param string $name Название
* @param array $arguments Параметры
*/
public static function __callStatic(string $name, array $arguments): mixed
{
match ($name) {
'db' => (new static)->__get('db'),
default => throw new exception("Не найдено свойство или функция: $name", 500)
};
}
/**
* Статический вызов
*
* @param string $name Название
* @param array $arguments Параметры
*/
public static function __callStatic(string $name, array $arguments): mixed
{
match ($name) {
'db' => (new static)->__get('db'),
default => throw new exception("Не найдено свойство или функция: $name", 500)
};
}
}

View File

@@ -0,0 +1,159 @@
<?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

@@ -0,0 +1,90 @@
<?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 $instance;
/**
* Прочитать
*
* @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->instance = collection::search(
static::$db->session,
sprintf(
<<<AQL
FOR d IN %s
FILTER d.key == '%s' && d.active == true
RETURN d
AQL,
self::COLLECTION,
$invite
)
);
return $instance;
}
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 new account();
}
}

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\account_model as account;
use mirzaev\site\account\models\account;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
@@ -23,7 +23,7 @@ use exception;
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class session_model extends core
final class session extends core
{
/**
* Коллекция
@@ -122,7 +122,8 @@ final class session_model extends core
}
}
public function __destruct() {
public function __destruct()
{
// Закрыть сессию
}
@@ -231,7 +232,7 @@ final class session_model extends core
*/
public function __set(string $name, mixed $value = null): void
{
$this->document->{$name} = $value;
$this->document->{$name} = $value;
}
/**
@@ -283,11 +284,9 @@ final class session_model extends core
*
* @param string $name Название
* @param array $arguments Аргументы
*
* @return void
*/
public function __call(string $name, array $arguments = []): mixed
public function __call(string $name, array $arguments = [])
{
return $this->document->{$name}($arguments);
if (method_exists($this, $name)) return $this->document->{$name}($arguments);
}
}

View File

@@ -5,7 +5,7 @@ declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\account_model as account;
use mirzaev\site\account\models\account;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
@@ -30,7 +30,7 @@ use stdClass;
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class vk_model extends core
final class vk extends core
{
/**
* Коллекция