разработана аутентификация и регистрация аккаунта

This commit is contained in:
2023-03-08 21:43:33 +10:00
parent 0d6ceef0f9
commit 87bd15640a
24 changed files with 1082 additions and 1353 deletions

View File

@@ -4,9 +4,6 @@ declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\vk;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
@@ -31,19 +28,162 @@ final class account extends core
public const COLLECTION = 'account';
/**
* Инстанция в базе данных
* Инстанция документа аккаунта в базе данных
*/
public ?_document $instance;
public ?_document $document;
/**
* Прочитать
* Конструктор
*
* 1. Проверяет связь сессии с аккаунтом
* 1.1. Если найдена связь, то возвращает связанный аккаунт (выход)
* 2. [authenticate === true] Проверяет наличие данных в буфере сессии
* 2.1 Если найден входной псевдоним и пароли совпадают, то аутентифицирует (выход)
* 2.2 [register === true] Если найдены данные для регистрации, то регистрирует (выход)
*
* @param ?session $session Инстанция сессии
* @param bool $authenticate Аутентифицировать аккаунт?
* @param bool $register Регистрировать аккаунт?
* @param array &$errors Реестр ошибок
*
* @return static Инстанция аккаунта
*/
public function __construct(?session $session = null, bool $authenticate = false, bool $register = false, array &$errors = [])
{
try {
if (isset($session)) {
// Получена инстанция сессии
if ($account = $session->account()) {
// Найден связанный с сессией аккаунт
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
return $this;
} else {
// Не найден связанный с сессией аккаунт
if ($authenticate) {
// Запрошена аутентификация
if (!empty($session->buffer['entry'])) {
// Найдены данные для идентификации в буфере сессии
if (!empty($session->buffer['entry']['login'])) {
// Найдены входной псевдоним в буфере сессии
if (($account = self::login($session->buffer['entry']['login'])) instanceof self) {
// Найден аккаунт (игнорируются ошибки)
if (isset($account->password) && $account->password === '') {
// Не имеет пароля аккаунт
// Проверка отсутствия переданного пароля
if (isset($session->buffer['entry']['password']) && $session->buffer['entry']['password'] !== '') throw new exception('Неправильный пароль');
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null]]);
return $this;
} else if (!empty($session->buffer['entry']['password'])) {
// Найден пароль в буфере сессии
if (sodium_crypto_pwhash_str_verify($account->password, $session->buffer['entry']['password'])) {
// Аутентифицирован аккаунт (прошёл проверку пароль, либо аккаунт не имеет пароля)
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null]]);
return $this;
} else throw new exception('Неправильный пароль');
} throw new exception('Неправильный пароль');
} else {
// Не найден аккаунт
if ($register) {
// Запрошена регистрация
if (!empty($session->buffer['entry']['invite'])) {
// Найден ключ приглашения в буфере сессии
// Проверка наличия переданного пароля
if (!isset($session->buffer['entry']['password'])) throw new exception('Не найден пароль в буфере сессии');
if (self::create(
[
'login' => $session->buffer['entry']['login'],
'password' => $session->buffer['entry']['password'] === ''
? ''
: sodium_crypto_pwhash_str(
$session->buffer['entry']['password'],
SODIUM_CRYPTO_PWHASH_OPSLIMIT_SENSITIVE,
SODIUM_CRYPTO_PWHASH_MEMLIMIT_SENSITIVE
)
],
$errors
)) {
// Зарегистрирован аккаунт
if (($account = self::login($session->buffer['entry']['login'], $errors)) instanceof self) {
// Найден аккаунт
// Инициализация инстанции документа аккаунта в базе данных
$this->document = $account->document;
// Связь сессии с аккаунтом
$session->connect($this, $errors);
// Удаление использованных данных из буфера сессии
$session->write(['entry' => ['password' => null, 'invite' => null]]);
return $this;
} else throw new exception('Не удалось аутентифицировать аккаунт после его регистрации');
} else throw new exception('Не удалось зарегистрировать аккаунт');
} else throw new exception('Не найден ключ приглашения в буфере сессии');
}
}
} else throw new exception('Не найден входной псевдоним в буфере сессии');
} else throw new exception('Не найдены данные для идентификации');
}
}
}
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
}
/**
* Найти по входному псевдониму
*
* @param string $login Входной псевдоним
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return ?self Инстанция аккаунта, если найден
* @return ?self Инстанция аккаунта, если аутентифицирован
*/
public static function read(string $login, array &$errors = []): ?self
public static function login(string $login, array &$errors = []): ?self
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
@@ -52,26 +192,25 @@ final class account extends core
// Инициализация инстанции аккаунта
$instance = new self;
// Поиск аккаунта
$instance->instance = collection::search(
// Поиск инстанции аккаунта в базе данных
$instance->document = collection::search(
static::$db->session,
sprintf(
<<<AQL
<<<'AQL'
FOR d IN %s
FILTER d.login == '%s'
RETURN d
FILTER d.login == '%s'
RETURN d
AQL,
self::COLLECTION,
$login
)
);
return $instance;
}
throw new exception('Не удалось инициализировать коллекцию');
if ($instance->document instanceof _document) return $instance;
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -86,75 +225,20 @@ final class account extends core
/**
* Создать
*
* @param array &$errors Журнал ошибок
* @param array $data Данные аккаунта
* @param array &$errors Реестр ошибок
*
* @return ?_document Инстанция аккаунта, если удалось создать
* @return bool Создан аккаунт?
*/
public static function create(array &$errors = []): ?_document
public static function create(array $data = [], array &$errors = []): bool
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Запись аккаунта в базу данных
$_id = document::write(static::$db->session, self::COLLECTION);
if ($account = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d._id == '$_id'
RETURN d
AQL,
self::COLLECTION
))) {
// Найден созданный аккаунт
return $account;
} else throw new exception('Не удалось создать аккаунт');
} else throw new exception('Не удалось инициализировать коллекцию');
if (collection::init(static::$db->session, self::COLLECTION))
if (document::write(static::$db->session, self::COLLECTION, $data)) return true;
else throw new exception('Не удалось создать аккаунт');
else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Связь аккаунта с аккаунтом ВКонтакте
*
* @param _document $account Инстанция аккаунта
* @param _document $vk Инстанция аккаунта ВКонтакте
* @param array &$errors Журнал ошибок
*
* @return bool Статус выполнения
*/
public static function connect(_document $account, _document $vk, array &$errors = []): bool
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, vk::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
) {
// Инициализированы коллекции
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, [
'_from' => $account->getId(),
'_to' => $vk->getId()
])) {
// Создано ребро: account -> vk
return true;
} else throw new exception('Не удалось создать ребро: account -> vk');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -167,56 +251,72 @@ final class account extends core
}
/**
* Поиск связанного аккаунта ВКонтакте
* Записать
*
* @param _document $account Инстанция аккаунта
* @param array &$errors Журнал ошибок
* Записывает свойство в инстанцию документа аккаунта из базы данных
*
* @return ?_document Инстанция аккаунта, если удалось найти
* @param string $name Название
* @param mixed $value Содержимое
*
* @return void
*/
public static function vk(_document $account, array &$errors = []): ?_document
public function __set(string $name, mixed $value = null): void
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, vk::COLLECTION)
&& collection::init(static::$db->session, self::COLLECTION . '_edge_' . vk::COLLECTION, true)
) {
// Инициализирована коллекция
$this->document->{$name} = $value;
}
if ($vk = collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
LET edge = (
FOR edge IN %s
FILTER edge._from == '%s'
SORT edge._key DESC
LIMIT 1
RETURN edge
)
FILTER document._id == edge[0]._to
LIMIT 1
RETURN document
AQL,
vk::COLLECTION,
self::COLLECTION . '_edge_' . vk::COLLECTION,
$account->getId()
))) {
// Найден аккаунт ВКонтакте
/**
* Прочитать
*
* Читает свойство из инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return mixed Данные свойства инстанции аккаунта или инстанции документа аккаунта из базы данных
*/
public function __get(string $name): mixed
{
return $this->document->{$name};
}
return $vk;
} else throw new exception('Не удалось найти аккаунт ВКонтакте');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
/**
* Проверить инициализированность
*
* Проверяет инициализированность свойства в инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return bool Свойство инициализировано?
*/
public function __isset(string $name): bool
{
return isset($this->document->{$name});
}
return null;
/**
* Удалить
*
* Деинициализировать свойство в инстанции документа аккаунта из базы данных
*
* @param string $name Название
*
* @return void
*/
public function __unset(string $name): void
{
unset($this->document->{$name});
}
/**
* Выполнить метод
*
* Выполнить метод в инстанции документа аккаунта из базы данных
*
* @param string $name Название
* @param array $arguments Аргументы
*/
public function __call(string $name, array $arguments = [])
{
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
}
}

View File

@@ -33,18 +33,30 @@ class core extends model
*/
protected static connection $db;
public function __construct(connection $db = null)
/**
* Конструктор
*
* @param bool $initialize Инициализировать контроллер?
* @param connection $db Инстанция соединения с базой данных
*/
public function __construct(bool $initialize = true, connection $db = null)
{
if (isset($db)) {
// Получена инстанция соединения с базой данных
parent::__construct($initialize);
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
} else {
// Не получена инстанция соединения с базой данных
if ($initialize) {
// Запрошена инициализация
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
if (isset($db)) {
// Получена инстанция соединения с базой данных
// Запись и инициализация соединения с базой данных
$this->__set('db', $db);
} else {
// Не получена инстанция соединения с базой данных
// Инициализация соединения с базой данных по умолчанию
$this->__get('db');
}
}
}

View File

@@ -27,7 +27,7 @@ final class password extends core
* Сгенерировать мнемонический пароль
*
* @param int $length Длина (количество слов)
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return ?string Пароль
*/
@@ -107,7 +107,7 @@ final class password extends core
return implode(' ', $password);
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -123,7 +123,7 @@ final class password extends core
* Сгенерировать классический пароль
*
* @param int $length Длина (количество символов)
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return ?string Пароль
*/
@@ -145,7 +145,7 @@ final class password extends core
return $password;
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),

View File

@@ -31,15 +31,15 @@ final class invite extends core
public const COLLECTION = 'invite';
/**
* Инстанция в базе данных
* Инстанция документа приглашения в базе данных
*/
public ?_document $instance;
public ?_document $document;
/**
* Прочитать
*
* @param string $invite Ключ приглашения
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return ?self Инстанция приглашения, если оно найдено
*/
@@ -53,25 +53,24 @@ final class invite extends core
$instance = new self;
// Поиск приглашения
$instance->instance = collection::search(
$instance->document = collection::search(
static::$db->session,
sprintf(
<<<AQL
FOR d IN %s
FILTER d.key == '%s' && d.active == true
RETURN d
FILTER d.key == '%s' && d.active == true
RETURN d
AQL,
self::COLLECTION,
$invite
)
);
return $instance;
}
throw new exception('Не удалось инициализировать коллекцию');
if ($instance->document instanceof _document) return $instance;
else throw new exception('Не удалось найти инстанцию приглашения в базе данных');
} throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -85,6 +84,6 @@ final class invite extends core
public function from(): ?account
{
return new account();
return null;
}
}

View File

@@ -31,7 +31,7 @@ final class session extends core
public const COLLECTION = 'session';
/**
* Данные сессии из базы данных
* Инстанция документа сессии в базе данных
*/
public _document $document;
@@ -42,7 +42,7 @@ final class session extends core
*
* @param ?string $hash Хеш сессии в базе данных
* @param ?int $expires Дата окончания работы сессии (используется при создании новой сессии)
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return static Инстанция сессии
*/
@@ -55,8 +55,8 @@ final class session extends core
if (isset($hash) && $session = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d.hash == '$hash' && d.expires > %d
RETURN d
FILTER d.hash == '$hash' && d.expires > %d && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
time()
@@ -68,8 +68,8 @@ final class session extends core
} else if ($session = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d.ip == '%s' && d.expires > %d
RETURN d
FILTER d.ip == '%s' && d.expires > %d && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
$_SERVER['REMOTE_ADDR'],
@@ -84,20 +84,21 @@ final class session extends core
// Запись сессии в базу данных
$_id = document::write(static::$db->session, self::COLLECTION, [
'ip' => $_SERVER['REMOTE_ADDR'],
'expires' => $expires ?? time() + 604800
'status' => 'active',
'expires' => $expires ?? time() + 604800,
'ip' => $_SERVER['REMOTE_ADDR']
]);
if ($session = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d._id == '$_id' && d.expires > %d
RETURN d
FILTER d._id == '$_id' && d.expires > %d && d.status == 'active'
RETURN d
AQL,
self::COLLECTION,
time()
))) {
// Найдена созданная сессия
// Найдена только что созданная сессия
// Запись хеша
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
@@ -112,7 +113,7 @@ final class session extends core
}
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -128,14 +129,16 @@ final class session extends core
}
/**
* Связь сессии с аккаунтом
* Инициализировать связб сессии с аккаунтом
*
* @param _document $account Инстанция аккаунта
* @param array &$errors Журнал ошибок
* Ищет связь сессии с аккаунтом, если не находит, то создаёт её
*
* @return bool Статус выполнения
* @param account $account Инстанция аккаунта
* @param array &$errors Реестр ошибок
*
* @return bool Связан аккаунт?
*/
public function connect(_document $account, array &$errors = []): bool
public function connect(account $account, array &$errors = []): bool
{
try {
if (
@@ -145,17 +148,30 @@ final class session extends core
) {
// Инициализирована коллекция
if (document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
'_from' => $this->document->getId(),
'_to' => $account->getId()
])) {
// Создано ребро: session -> account
if (
collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
FILTER document._from == '%s' && document._to == '%s'
LIMIT 1
RETURN document
AQL,
self::COLLECTION . '_edge_' . account::COLLECTION,
$this->document->getId(),
$account->getId()
)) instanceof _document
|| document::write(static::$db->session, self::COLLECTION . '_edge_' . account::COLLECTION, [
'_from' => $this->document->getId(),
'_to' => $account->getId()
])
) {
// Найдено, либо создано ребро: session -> account
return true;
} else throw new exception('Не удалось создать ребро: session -> account');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -168,13 +184,13 @@ final class session extends core
}
/**
* Поиск связанного аккаунта
* Найти связанный аккаунт
*
* @param array &$errors Журнал ошибок
* @param array &$errors Реестр ошибок
*
* @return ?_document Инстанция аккаунта, если удалось найти
* @return ?account Инстанция аккаунта, если удалось найти
*/
public function account(array &$errors = []): ?_document
public function account(array &$errors = []): ?account
{
try {
if (
@@ -184,31 +200,34 @@ final class session extends core
) {
// Инициализированы коллекции
if ($account = collection::search(static::$db->session, sprintf(
// Инициализация инстанции аккаунта
$account = new account;
// Поиск инстанции аккаунта в базе данных
$account->document = collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
LET edge = (
FOR edge IN %s
FILTER edge._from == '%s'
SORT edge._key DESC
LET edge = (
FOR edge IN %s
FILTER edge._from == '%s'
SORT edge._key DESC
LIMIT 1
RETURN edge
)
FILTER document._id == edge[0]._to
LIMIT 1
RETURN edge
)
FILTER document._id == edge[0]._to
LIMIT 1
RETURN document
RETURN document
AQL,
account::COLLECTION,
self::COLLECTION . '_edge_' . account::COLLECTION,
$this->document->getId()
))) {
// Найден аккаунт
$this->getId()
));
return $account;
} else throw new exception('Не удалось найти аккаунт');
if ($account->document instanceof _document) return $account;
else throw new exception('Не удалось найти инстанцию аккаунта в базе данных');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
@@ -220,6 +239,47 @@ final class session extends core
return null;
}
/**
* Записать в буфер сессии
*
* @param array $data Данные для записи
* @param array &$errors Реестр ошибок
*
* @return bool Записаны данные в буфер сессии?
*/
public function write(array $data, array &$errors = []): bool
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Проверка инициализированности инстанции документа из базы данных
if (!isset($this->document)) throw new exception('Не инициализирована инстанция документа из базы данных');
// Запись параметров в инстанцию документа из базы данных
$this->document->buffer = array_replace_recursive($this->document->buffer ?? [], $data);
if (document::update(static::$db->session, $this->document)) {
// Записано обновление
return true;
}
throw new exception('Не удалось записать данные в буфер сессии');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в реестр ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return false;
}
/**
* Записать
*
@@ -287,6 +347,6 @@ final class session extends core
*/
public function __call(string $name, array $arguments = [])
{
if (method_exists($this, $name)) return $this->document->{$name}($arguments);
if (method_exists($this->document, $name)) return $this->document->{$name}($arguments);
}
}

View File

@@ -1,555 +0,0 @@
<?php
declare(strict_types=1);
namespace mirzaev\site\account\models;
// Файлы проекта
use mirzaev\site\account\models\account;
// Фреймворк ArangoDB
use mirzaev\arangodb\collection,
mirzaev\arangodb\document;
// Фреймворк ВКонтакте
use mirzaev\vk\robots\user as robot;
// Библиотека для ArangoDB
use ArangoDBClient\Document as _document;
// Библиотека браузера
use GuzzleHttp\Client as browser;
// Встроенные библиотеки
use exception;
use stdClass;
/**
* Модель аккаунта ВКонтакте
*
* @package mirzaev\site\account\models
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
*/
final class vk extends core
{
/**
* Коллекция
*/
public const COLLECTION = 'vk';
/**
* Инициализация
*
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
*/
public static function initialization(string $response = '', array &$errors = []): ?_document
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Инициализация данных аккаунта ВКонтакте
$data = json_decode($response);
if ($account = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d.id == $data->user_id
RETURN d
AQL,
self::COLLECTION
))) {
// Найден аккаунт ВКонтакте
return $account;
} else {
// Не найден аккаунт ВКонтакте
return self::create($response, $errors);
}
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Создание
*
* @param string $response Ответ сервера ВКонтакте с данными аккаунта
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось создать
*/
public static function create(string $response = '', array &$errors = []): ?_document
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
// Запись аккаунта в базу данных
$_id = document::write(static::$db->session, self::COLLECTION);
if ($account = collection::search(static::$db->session, sprintf(
<<<AQL
FOR d IN %s
FILTER d._id == '$_id'
RETURN d
AQL,
self::COLLECTION
))) {
// Найден созданный аккаунт ВКонтакте
if (document::update(static::$db->session, $account)) {
// Записано обновление
// Запись данных об аккаунте ВКонтакте и возврат (bool)
return self::update($account, json_decode($response), $errors);
}
}
throw new exception('Не удалось создать аккаунт ВКонтакте');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запросить ключ
*
* @param string $code Код полученный от ВКонтакте
* @param array &$errors Журнал ошибок
*
* @return ?string Тело ответа, если получен код ответа 200
*/
public static function key(string $code = '', array &$errors = []): ?string
{
try {
// Инициализация браузера
$browser = new browser();
// Запрос
$response = $browser->request('GET', "https://oauth.vk.com/access_token?client_id=51447080&client_secret=KYlk0nGELW0A9ds7NQi6&redirect_uri=https://mirzaev.sexy/account/vk/connect&code=$code");
if ($response->getStatusCode() === 200) {
// Ответ сервера: 200
return (string) $response->getBody();
} else throw new exception('Не удалось получить ключ ВКонтакте (' . $response->getStatusCode() . ')');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Поиск связанного аккаунта
*
* @param _document $vk Инстанция аккаунта ВКонтакте
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция аккаунта, если удалось найти
*/
public static function account(_document $vk, array &$errors = []): ?_document
{
try {
if (
collection::init(static::$db->session, self::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION)
&& collection::init(static::$db->session, account::COLLECTION . '_edge_' . self::COLLECTION, true)
) {
// Инициализированы коллекции
if ($account = collection::search(static::$db->session, sprintf(
<<<AQL
FOR document IN %s
LET edge = (
FOR edge IN %s
FILTER edge._to == '%s'
SORT edge._key DESC
LIMIT 1
RETURN edge
)
FILTER document._id == edge[0]._from
LIMIT 1
RETURN document
AQL,
account::COLLECTION,
account::COLLECTION . '_edge_' . self::COLLECTION,
$vk->getId()
))) {
// Найден аккаунт
return $account;
} else throw new exception('Не удалось найти аккаунт');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Запрос данных аккаунта ВКонтакте с серверов ВКонтакте
*
* @param robot $vk Инстанция аккаунта ВКонтакте
* @param array &$errors Журнал ошибок
*
* @return ?stdClass Данные аккаунта ВКонтакте, если получены
*/
public static function parse(robot $vk, array &$errors = []): ?stdClass
{
try {
// Запрос к API-серверу ВКонтакте
$response = $vk->user->get(fields: [
'activities',
'about',
// 'blacklisted',
// 'blacklisted_by_me',
'books',
'bdate',
'can_be_invited_group',
'can_post',
'can_see_all_posts',
'can_see_audio',
'can_send_friend_request',
'can_write_private_message',
'career',
'common_count',
'connections',
'contacts',
'city',
'country',
'crop_photo',
'domain',
'education',
'exports',
'followers_count',
'friend_status',
'has_photo',
'has_mobile',
'home_town',
'photo_50',
'photo_100',
'photo_200',
'photo_200_orig',
'photo_400_orig',
'photo_max',
'photo_max_orig',
'sex',
'site',
'schools',
'screen_name',
'status',
'verified',
'games',
'interests',
'is_favorite',
'is_friend',
'is_hidden_from_feed',
'last_seen',
'maiden_name',
'military',
'movies',
'music',
'nickname',
'occupation',
'online',
'personal',
'photo_id',
'quotes',
'relation',
'relatives',
'timezone',
'tv',
'universities'
])[0];
if (!empty($response)) {
// Получен ответ
return $response;
}
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
/**
* Обновление данных аккаунта ВКонтакте
*
* Все файлы (аватар, например) будут скачаны на сервер
*
* @param _document $vk Инстанция аккаунта ВКонтакте
* @param stdClass $data Информация об аккаунте (self::parse() или json_decode())
* @param array &$errors Журнал ошибок
*
* @return ?_document Инстанция аккаунта ВКонтакте, если удалось обновить
*/
public static function update(_document $vk, stdClass $data, array &$errors = []): ?_document
{
try {
if (collection::init(static::$db->session, self::COLLECTION)) {
// Инициализирована коллекция
if (empty($vk->id) and isset($data->user_id) || isset($data->id)) {
// Получен идентификатор
// Запись
$vk->id = $data->user_id ?? $data->id;
// Удаление из списка необработанных
unset($data->user_id, $data->id);
} else if (empty($vk->id)) throw new exception('Не удалось найти идентификатор аккаунта ВКонтакте');
if (isset($data->access_token, $data->expires_in)) {
// Получен ключ
// Запись
$vk->access = [
'key' => $data->access_token,
'expires' => $data->expires_in
];
// Удаление из списка необработанных
unset($data->access_token, $data->expires_in);
}
// Инициализация браузера
$browser = new browser();
// Инициализация директории с обложкой
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
mkdir($path, 0775, true);
if (isset($data->photo_50)) {
// Получено изображение 50x50
if ($browser->get($data->photo_50, ['sink' => $file = "$path/50x50.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'50x50' => ($vk->cover['50x50'] ?? []) +
[
'source' => $data->photo_50,
'public' => "/storage/$vk->id/cover/50x50.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение 50x50 с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_50);
}
// Инициализация директории с обложкой
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
mkdir($path, 0775, true);
if (isset($data->photo_100)) {
// Получено изображение 100x100
if ($browser->get($data->photo_100, ['sink' => $file = "$path/100x100.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'100x100' => ($vk->cover['100x100'] ?? []) +
[
'source' => $data->photo_100,
'public' => "/storage/$vk->id/cover/100x100.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение 100x100 с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_100);
}
// Инициализация директории с обложкой
if (!file_exists($path = INDEX . DIRECTORY_SEPARATOR . 'storage' . DIRECTORY_SEPARATOR . $vk->id . DIRECTORY_SEPARATOR . 'cover' . DIRECTORY_SEPARATOR))
mkdir($path, 0775, true);
if (isset($data->photo_200)) {
// Получено изображение 200x200
if ($browser->get($data->photo_200, ['sink' => $file = "$path/200x200.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'200x200' => ($vk->cover['200x200'] ?? []) +
[
'source' => $data->photo_200,
'public' => "/storage/$vk->id/cover/200x200.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение 200x200 с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_200);
}
if (isset($data->photo_200_orig)) {
// Получено изображение 200x
if ($browser->get($data->photo_200_orig, ['sink' => $file = "$path/200x.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'200x' => ($vk->cover['200x'] ?? []) +
[
'source' => $data->photo_200_orig,
'public' => "/storage/$vk->id/cover/200x.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение 200x с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_200_orig);
}
if (isset($data->photo_400_orig)) {
// Получено изображение 400x
if ($browser->get($data->photo_400_orig, ['sink' => $file = "$path/400x.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'400x' => ($vk->cover['400x'] ?? []) +
[
'source' => $data->photo_400_orig,
'public' => "/storage/$vk->id/cover/400x.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение 400x с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_400_orig);
}
if (isset($data->photo_max)) {
// Получено изображение MAXxMAX
if ($browser->get($data->photo_max, ['sink' => $file = "$path/MAXxMAX.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'MAXxMAX' => ($vk->cover['MAXxMAX'] ?? []) +
[
'source' => $data->photo_max,
'public' => "/storage/$vk->id/cover/MAXxMAX.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение MAXxMAX с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_max);
}
if (isset($data->photo_max_orig)) {
// Получено изображение MAXx
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'MAXx' => ($vk->cover['MAXx'] ?? []) +
[
'source' => $data->photo_max_orig,
'public' => "/storage/$vk->id/cover/MAXx.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_max_orig);
}
if (isset($data->crop_photo)) {
// Получено изображение MAXx
if ($browser->get($data->photo_max_orig, ['sink' => $file = "$path/MAXx.jpg"])->getStatusCode() === 200)
$vk->cover =
($vk->cover ?? []) +
[
'MAXx' => ($vk->cover['MAXx'] ?? []) +
[
'source' => $data->photo_max_orig,
'public' => "/storage/$vk->id/cover/MAXx.jpg",
'local' => $file,
]
];
else throw new exception('Не удалось получить изображение MAXx с серверов ВКонтакте');
// Удаление из списка необработанных
unset($data->photo_max_orig);
}
// Перебор оставшихся параметров
foreach ($data as $key => $value) $vk->{$key} = $value;
if (document::update(static::$db->session, $vk)) {
// Записано обновление
return $vk;
} else throw new exception('Не удалось записать данные аккаунта ВКонтакте');
} else throw new exception('Не удалось инициализировать коллекцию');
} catch (exception $e) {
// Запись в журнал ошибок
$errors[] = [
'text' => $e->getMessage(),
'file' => $e->getFile(),
'line' => $e->getLine(),
'stack' => $e->getTrace()
];
}
return null;
}
}