339 lines
10 KiB
PHP
Executable File
339 lines
10 KiB
PHP
Executable File
<?php
|
||
|
||
declare(strict_types=1);
|
||
|
||
namespace mirzaev\arming_bot\models;
|
||
|
||
// Files of the project
|
||
use mirzaev\arming_bot\models\account,
|
||
mirzaev\arming_bot\models\enumerations\session as verification,
|
||
mirzaev\arming_bot\models\traits\status,
|
||
mirzaev\arming_bot\models\traits\document as arangodb_document_trait,
|
||
mirzaev\arming_bot\models\interfaces\document as arangodb_document_interface;
|
||
|
||
// Framework for ArangoDB
|
||
use mirzaev\arangodb\collection,
|
||
mirzaev\arangodb\document;
|
||
|
||
// Library для ArangoDB
|
||
use ArangoDBClient\Document as _document;
|
||
|
||
// Built-in libraries
|
||
use exception;
|
||
|
||
/**
|
||
* Model of a session
|
||
*
|
||
* @package mirzaev\arming_bot\models
|
||
* @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
|
||
*/
|
||
final class session extends core implements arangodb_document_interface
|
||
{
|
||
use status, arangodb_document_trait;
|
||
|
||
/**
|
||
* Name of the collection in ArangoDB
|
||
*/
|
||
final public const string COLLECTION = 'session';
|
||
|
||
/**
|
||
* Type of session verification(
|
||
*/
|
||
public const verification VERIFICATION = verification::hash_else_address;
|
||
|
||
/**
|
||
* Constructor of an instance
|
||
*
|
||
* Initialize of a session and write them to the $this->document property
|
||
*
|
||
* @param ?string $hash Hash of the session in ArangoDB
|
||
* @param ?int $expires Date of expiring of the session (used for creating a new session)
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return static instance of the ArangoDB document of session
|
||
*/
|
||
public function __construct(?string $hash = null, ?int $expires = null, array &$errors = [])
|
||
{
|
||
try {
|
||
if (collection::init(static::$arangodb->session, self::COLLECTION)) {
|
||
// Initialized the collection
|
||
|
||
if (isset($hash) && $document = $this->hash($hash, $errors)) {
|
||
// Found an instance of the ArangoDB document of session and received a session hash
|
||
|
||
// Writing document instance of the session from ArangoDB to the property of the implementing object
|
||
$this->document = $document;
|
||
} else if (static::VERIFICATION === verification::hash_else_address && $document = $this->address($_SERVER['REMOTE_ADDR'], $errors)) {
|
||
// Found an instance of the ArangoDB document of session and received a session hash
|
||
|
||
// Writing document instance of the session from ArangoDB to the property of the implementing object
|
||
$this->document = $document;
|
||
} else {
|
||
// Not found an instance of the ArangoDB document of session
|
||
|
||
// Initializing a new session and write they into ArangoDB
|
||
$_id = document::write($this::$arangodb->session, self::COLLECTION, [
|
||
'active' => true,
|
||
'expires' => $expires ?? time() + 604800,
|
||
'address' => $_SERVER['REMOTE_ADDR'],
|
||
'x-forwarded-for' => $_SERVER['HTTP_X_FORWARDED_FOR'] ?? null,
|
||
'referer' => $_SERVER['HTTP_REFERER'] ?? null,
|
||
'useragent' => $_SERVER['HTTP_USER_AGENT'] ?? null
|
||
]);
|
||
|
||
if ($session = collection::search($this::$arangodb->session, sprintf(
|
||
<<<AQL
|
||
FOR d IN %s
|
||
FILTER d._id == '%s' && d.expires > %d && d.active == true
|
||
RETURN d
|
||
AQL,
|
||
self::COLLECTION,
|
||
$_id,
|
||
time()
|
||
))) {
|
||
// Found an instance of just created new session
|
||
|
||
// Generate a hash and write into an instance of the ArangoDB document of session property
|
||
$session->hash = sodium_bin2hex(sodium_crypto_generichash($_id));
|
||
|
||
if (document::update($this::$arangodb->session, $session)) {
|
||
// Is writed update
|
||
|
||
// Writing document instance of the session from ArangoDB to the property of the implementing object
|
||
$this->document = $session;
|
||
} else throw new exception('Could not write the session data');
|
||
} else throw new exception('Could not create or find just created session');
|
||
}
|
||
} else throw new exception('Could not initialize the collection');
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Search for a connected account
|
||
*
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return account|null An object implementing the account instance from the database, if found
|
||
*/
|
||
public function account(array &$errors = []): ?account
|
||
{
|
||
try {
|
||
if (collection::init(core::$arangodb->session, static::COLLECTION)) {
|
||
if (collection::init(core::$arangodb->session, 'connect', true)) {
|
||
if (collection::init(core::$arangodb->session, account::COLLECTION)) {
|
||
// Инициализирована коллекция
|
||
|
||
if ($document = collection::search(
|
||
core::$arangodb->session,
|
||
sprintf(
|
||
<<<AQL
|
||
FOR v IN INBOUND "%s" GRAPH sessions
|
||
SORT v.created DESC
|
||
LIMIT 1
|
||
RETURN v
|
||
AQL,
|
||
$this->getId(),
|
||
)
|
||
)) {
|
||
// Найден аккаунт
|
||
|
||
// Инициализация объекта аккаунта
|
||
$account = new account;
|
||
|
||
// Запись инстанции документа в объект
|
||
$account->__document($document);
|
||
|
||
// Exit (success)
|
||
return $account;
|
||
} else return null;
|
||
} else throw new exception('Failed to initialize document collection: ' . account::COLLECTION);
|
||
} else throw new exception('Failed to initialize edge collection: connect');
|
||
} else throw new exception('Failed to initialize document collection: ' . static::COLLECTION);
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
// Exit (fail)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Connect account to session
|
||
*
|
||
* @param account $account Account
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return string|null The identifier of the created edge of the "connect" collection, if created
|
||
*/
|
||
public function connect(account $account, array &$errors = []): ?string
|
||
{
|
||
try {
|
||
if (collection::init(core::$arangodb->session, static::COLLECTION)) {
|
||
if (collection::init(core::$arangodb->session, 'connect', true)) {
|
||
if (collection::init(core::$arangodb->session, account::COLLECTION)) {
|
||
// Collections initialized
|
||
|
||
// Writing document and exit (success)
|
||
return document::write(
|
||
core::$arangodb->session,
|
||
'connect',
|
||
[
|
||
'_from' => $account->getId(),
|
||
'_to' => $this->document->getId()
|
||
]
|
||
);
|
||
} else throw new exception('Failed to initialize document collection: ' . account::COLLECTION);
|
||
} else throw new exception('Failed to initialize edge collection: connect');
|
||
} else throw new exception('Failed to initialize document collection: ' . static::COLLECTION);
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
// Exit (fail)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Search by hash
|
||
*
|
||
* Search for the session in ArangoDB by hash
|
||
*
|
||
* @param string $hash Hash of the session in ArangoDB
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return _document|null instance of document of the session in ArangoDB
|
||
*/
|
||
public static function hash(string $hash, array &$errors = []): ?_document
|
||
{
|
||
try {
|
||
if (collection::init(core::$arangodb->session, static::COLLECTION)) {
|
||
// Collection initialized
|
||
|
||
// Search the session data in ArangoDB
|
||
return collection::search(static::$arangodb->session, sprintf(
|
||
<<<AQL
|
||
FOR d IN %s
|
||
FILTER d.hash == '%s' && d.expires > %d && d.active == true
|
||
RETURN d
|
||
AQL,
|
||
static::COLLECTION,
|
||
$hash,
|
||
time()
|
||
));
|
||
} else throw new exception('Failed to initialize document collection: ' . static::COLLECTION);
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
// Exit (fail)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Search by IP-address
|
||
*
|
||
* Search for the session in ArangoDB by IP-address
|
||
*
|
||
* @param string $address IP-address writed to the session in ArangoDB
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return _document|null instance of document of the session in ArangoDB
|
||
*/
|
||
public static function address(string $address, array &$errors = []): ?_document
|
||
{
|
||
try {
|
||
if (collection::init(core::$arangodb->session, static::COLLECTION)) {
|
||
// Collection initialized
|
||
|
||
// Search the session data in ArangoDB
|
||
return collection::search(static::$arangodb->session, sprintf(
|
||
<<<AQL
|
||
FOR d IN %s
|
||
FILTER d.address == '%s' && d.expires > %d && d.active == true
|
||
RETURN d
|
||
AQL,
|
||
static::COLLECTION,
|
||
$address,
|
||
time()
|
||
));
|
||
} else throw new exception('Failed to initialize document collection: ' . static::COLLECTION);
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
// Exit (fail)
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Write to buffer of the session
|
||
*
|
||
* @param array $data Data for merging
|
||
* @param array &$errors Registry of errors
|
||
*
|
||
* @return bool Is data has written into the session buffer?
|
||
*/
|
||
public function write(array $data, array &$errors = []): bool
|
||
{
|
||
try {
|
||
if (collection::init($this::$arangodb->session, self::COLLECTION)) {
|
||
// Initialized the collection
|
||
|
||
// An instance of the ArangoDB document of session is initialized?
|
||
if (!isset($this->document)) throw new exception('An instance of the ArangoDB document of session is not initialized');
|
||
|
||
// Write data into buffwer of an instance of the ArangoDB document of session
|
||
$this->document->buffer = array_replace_recursive(
|
||
$this->document->buffer ?? [],
|
||
[$_SERVER['INTERFACE'] => array_replace_recursive($this->document->buffer[$_SERVER['INTERFACE']] ?? [], $data)]
|
||
);
|
||
|
||
// Write to ArangoDB and exit (success)
|
||
return document::update($this::$arangodb->session, $this->document) ? true : throw new exception('Не удалось записать данные в буфер сессии');
|
||
} else throw new exception('Could not initialize the collection');
|
||
} catch (exception $e) {
|
||
// Write to the registry of errors
|
||
$errors[] = [
|
||
'text' => $e->getMessage(),
|
||
'file' => $e->getFile(),
|
||
'line' => $e->getLine(),
|
||
'stack' => $e->getTrace()
|
||
];
|
||
}
|
||
|
||
return false;
|
||
}
|
||
}
|