diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..8f3aadb
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+./vendor
diff --git a/mirzaev/calculator/system/controllers/accounts_controller.php b/mirzaev/calculator/system/controllers/accounts_controller.php
new file mode 100644
index 0000000..a6205f2
--- /dev/null
+++ b/mirzaev/calculator/system/controllers/accounts_controller.php
@@ -0,0 +1,157 @@
+
+ */
+final class accounts_controller extends core
+{
+ /**
+ * Страница профиля
+ *
+ * @param array $params
+ * @return void
+ */
+ public function index(array $vars = []): ?string
+ {
+ return null;
+ }
+
+ /**
+ * Регистрация
+ *
+ * @param array $vars Параметры запроса
+ *
+ * @return string|null HTML-документ
+ */
+ public function registration(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['account' => []];
+
+ if ($vars['account'] = accounts::registration(email: $vars['email'] ?? null, password: $vars['password'] ?? null, errors: $vars['errors']['account'])) {
+ // Удалось зарегистрироваться
+
+ if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), $vars)) {
+ // Удалось аутентифицироваться
+ } else {
+ // Не удалось аутентифицироваться
+
+ // Запись кода ответа
+ http_response_code(401);
+ }
+ } else {
+ // Не удалось зарегистрироваться
+
+ // Запись кода ответа
+ http_response_code(401);
+ }
+
+ // Генерация представления
+ return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
+ }
+
+ /**
+ * Аутентификация
+ *
+ * @param array $vars Параметры запроса
+ *
+ * @return string|null HTML-документ
+ */
+ public function authentication(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['account' => []];
+
+ if ($vars['account'] = accounts::authentication($vars['email'] ?? null, $vars['password'] ?? null, (bool) ($vars['remember'] ?? false), errors: $vars['errors']['account'])) {
+ // Удалось аутентифицироваться
+ } else {
+ // Не удалось аутентифицироваться
+
+ // Запись кода ответа
+ http_response_code(401);
+ }
+
+ // Генерация представления
+ return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
+ }
+
+ /**
+ * Деаутентификация
+ *
+ * @param array $vars Параметры запроса
+ *
+ * @return string|null HTML-документ
+ */
+ public function deauthentication(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['account' => []];
+
+ if (accounts::deauthentication(errors: $vars['errors']['account'])) {
+ // Удалось деаутентифицироваться
+
+ // Деинициализация аккаунта
+ $vars['account'] = null;
+ } else {
+ // Не удалось деаутентифицироваться
+
+ // Запись кода ответа
+ http_response_code(500);
+ }
+
+ // Генерация представления
+ return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
+ }
+
+ /**
+ * Данные аккаунта
+ *
+ * Если информацию запрашивает администратор, то вернётся вся, иначе только разрешённая публично
+ *
+ * @param array $vars Параметры запроса
+ *
+ * @return string JSON-документ
+ */
+ public function data(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['account' => []];
+
+ if ($account = accounts::read(['id' => $vars['id']], $vars['errors'])) {
+ // Найдены данные запрашиваемого аккаунта
+
+ // Инициализация аккаунта
+ $vars['account'] = accounts::account($vars['errors']);
+
+ if ($vars['account'] && $vars['account']['permissions']['accounts'] ?? 0 === 1) {
+ // Удалось аутентифицироваться и пройдена проверка авторизации
+ } else {
+ // Не удалось аутентифицироваться
+
+ // Удаление запрещённых к публикации полей
+ $account['password'] = $account['hash'] = $account['time'] = null;
+ }
+
+ // Генерация ответа
+ return json_encode($account ?? '');
+ } else {
+ // Не найдены данные запрашиваемого аккаунта
+
+ // Запись кода ответа
+ http_response_code(404);
+ }
+
+ return null;
+ }
+}
diff --git a/mirzaev/calculator/system/controllers/calculator_controller.php b/mirzaev/calculator/system/controllers/calculator_controller.php
index 27e5af7..9c54d04 100644
--- a/mirzaev/calculator/system/controllers/calculator_controller.php
+++ b/mirzaev/calculator/system/controllers/calculator_controller.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core;
+use mirzaev\calculator\models\calculators_model as calculators;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
@@ -119,4 +120,71 @@ final class calculator_controller extends core
// Генерация представления
return $this->view->render(DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'modules' . DIRECTORY_SEPARATOR . 'calculators' . DIRECTORY_SEPARATOR . 'laser.html', $vars);
}
+
+ /**
+ * Рассчёт
+ *
+ * Генерирует ответ в виде ['expenses' => 0, 'income' => 0, 'profit' => 0]
+ *
+ * @param array $vars Параметры
+ *
+ * @todo
+ * 1. Отправлять данные в зависимости от разрешения (обычным пользователям только expenses)
+ */
+ public function calculate(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['calculators' => []];
+
+ // Инициализация калькуляторов из тела запроса (подразумевается, что там массивы с параметрами)
+ $calculators = json_decode(file_get_contents('php://input'), true);
+
+ // Инициализация переменных для буфера вывода
+ $machines = $managers = $engineers = $operators = 0;
+
+ foreach ($calculators as $i => $calculator) {
+ // Перебор калькуляторов
+
+ foreach (['type'] as $parameter) {
+ // Перебор мета-параметров
+
+ // Инициализация общего параметра
+ extract([$parameter => $calculator[$parameter] ?? null]);
+
+ // Инициализация параметра для обработчика калькулятора
+ unset($calculator[$parameter]);
+ }
+
+ // Инициализация номера калькулятора в его категории
+ $number = count($vars['errors']['calculators'][$type] ?? []);
+
+ // Инициализация журнала ошибок для калькулятора
+ $calculator['errors'] = [];
+
+ // Инициализация журнала ошибок для буфера вывода
+ $vars['errors']['calculators'][$type][$number] = &$calculator['errors'];
+
+ // Инициализация буфера параметров
+ $parameters = [];
+
+ // Инициализация параметра типа покупателя (подразумевается, что если не "entity", то "individual")
+ $parameters['company'] = $calculator['buyer'] === 'entity';
+ unset($calculator['buyer']);
+
+ // Перенос остальных параметров в буфер параметров
+ $parameters += $calculator;
+
+ // var_dump($parameters);
+
+ // Расчёт
+ [$machines, $managers, $engineers, $operators] = calculators::$type(...$parameters);
+ }
+
+ return json_encode([
+ 'machines' => $machines,
+ 'managers' => $managers,
+ 'engineers' => $engineers,
+ 'operators' => $operators
+ ]);
+ }
}
diff --git a/mirzaev/calculator/system/controllers/core.php b/mirzaev/calculator/system/controllers/core.php
index 0784cf7..7931120 100644
--- a/mirzaev/calculator/system/controllers/core.php
+++ b/mirzaev/calculator/system/controllers/core.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers;
use mirzaev\calculator\views\manager;
+use mirzaev\calculator\models\core as models;
use mirzaev\minimal\controller;
@@ -25,6 +26,9 @@ class core extends controller
{
parent::__construct();
+ // Инициализация ядра моделей (соединение с базой данных...)
+ new models();
+
$this->view = new manager;
}
}
diff --git a/mirzaev/calculator/system/controllers/errors_controller.php b/mirzaev/calculator/system/controllers/errors_controller.php
index dd3e205..b8935e1 100644
--- a/mirzaev/calculator/system/controllers/errors_controller.php
+++ b/mirzaev/calculator/system/controllers/errors_controller.php
@@ -19,12 +19,18 @@ final class errors_controller extends core
{
public function error404()
{
+ // Запись кода ответа
+ http_response_code(404);
+
// Генерация представления
return 'Не найдено (404)';
}
public function error500()
{
+ // Запись кода ответа
+ http_response_code(500);
+
// Генерация представления
return 'Внутренняя ошибка (500)';
}
diff --git a/mirzaev/calculator/system/controllers/main_controller.php b/mirzaev/calculator/system/controllers/main_controller.php
index b4f546a..e968d44 100644
--- a/mirzaev/calculator/system/controllers/main_controller.php
+++ b/mirzaev/calculator/system/controllers/main_controller.php
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace mirzaev\calculator\controllers;
use mirzaev\calculator\controllers\core;
+use mirzaev\calculator\models\accounts_model as accounts;
use Twig\Loader\FilesystemLoader;
use Twig\Environment as view;
@@ -17,9 +18,15 @@ use Twig\Environment as view;
*/
final class main_controller extends core
{
- public function index(array $params = [])
+ public function index(array $vars = [])
{
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['account' => []];
+
+ // Проверка аутентифицированности
+ $vars['account'] = accounts::account($vars['errors']);
+
// Генерация представления
- return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html');
+ return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', $vars);
}
}
diff --git a/mirzaev/calculator/system/controllers/settings_controller.php b/mirzaev/calculator/system/controllers/settings_controller.php
new file mode 100644
index 0000000..8b5be96
--- /dev/null
+++ b/mirzaev/calculator/system/controllers/settings_controller.php
@@ -0,0 +1,91 @@
+
+ */
+final class settings_controller extends core
+{
+ /**
+ * Настройки (страница)
+ *
+ * HTML-документ со списком настроек
+ *
+ * @param array $vars Параметры
+ */
+ public function index(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['settings' => []];
+
+ // Инициализация аккаунта
+ $vars['account'] = accounts::account($vars['errors']);
+
+ // Генерация представления
+ return $this->view->render(DIRECTORY_SEPARATOR . 'settings' . DIRECTORY_SEPARATOR . 'index.html', $vars);
+ }
+
+ /**
+ * Записать
+ *
+ * @param array $vars Параметры
+ */
+ public function write(array $vars = []): ?bool
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['settings' => []];
+
+ // Инициализация аккаунта
+ $vars['account'] = accounts::account($vars['errors']);
+
+
+ if ($vars['account'] && $vars['account']['permissions']['settings'] ?? 0 === 1) {
+ // Удалось аутентифицироваться и пройдена проверка авторизации
+
+ foreach ($vars['settings'] ?? [] as $name => $value) {
+ // Перебор настроек
+
+ settings::write($name, $value);
+
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Прочитать
+ *
+ * @param array $vars Параметры
+ */
+ public function read(array $vars = []): ?string
+ {
+ // Инициализация журнала ошибок
+ $vars['errors'] = ['settings' => []];
+
+ // Инициализация аккаунта
+ $vars['account'] = accounts::account($vars['errors']);
+
+ // Инициализация буфера вывода
+ $settings = [];
+
+ foreach ($vars['settings'] ?? [] as $name) {
+ // Перебор настроек
+
+ $settings[] = settings::read($name, $vars['account'] && $vars['account']['permissions']['settings'] ?? 0 === 1);
+ }
+
+ return json_encode($settings);
+ }
+}
diff --git a/mirzaev/calculator/system/models/accounts_model.php b/mirzaev/calculator/system/models/accounts_model.php
new file mode 100644
index 0000000..acb8e51
--- /dev/null
+++ b/mirzaev/calculator/system/models/accounts_model.php
@@ -0,0 +1,531 @@
+
+ */
+final class accounts_model extends core
+{
+ /**
+ * Регистрация
+ *
+ * @param string $name Входной псевдоним
+ * @param string $email Почта
+ * @param string $password Пароль (password)
+ * @param bool $authentication Автоматическая аутентификация в случае успешной регистрации
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array|bool Аккаунт, если удалось аутентифицироваться
+ */
+ public static function registration(string $name = null, string $email = null, string $password, array &$errors = []): array
+ {
+ try {
+ if (static::account($errors)) {
+ // Аутентифицирован пользователь
+
+ // Запись ошибки
+ throw new exception('Уже аутентифицирован');
+ }
+
+ if (empty($account = static::read(['name' => $name]) or $account = static::read(['email' => $email]))) {
+ // Не удалось найти аккаунт
+
+ if (static::write($name, $email, $password, $errors)) {
+ // Удалось зарегистрироваться
+
+ return $account;
+ }
+ } else {
+ // Удалось найти аккаунт
+
+ return $account;
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return [];
+ }
+
+ /**
+ * Аутентификация
+ *
+ * @param string $login Входной псевдоним
+ * @param string $password Пароль (password)
+ * @param bool $remember Функция "Запомнить меня" - увеличенное время хранения cookies
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Аккаунт (если не найден, то пустой массив)
+ */
+ public static function authentication(string $login, string $password, bool $remember = false, array &$errors = []): array
+ {
+ try {
+ if (static::account($errors)) {
+ // Аутентифицирован пользователь
+
+ // Запись ошибки
+ throw new exception('Уже аутентифицирован');
+ }
+
+
+ if (empty($account = static::read(['name' => $login]) or $account = static::read(['email' => $login]))) {
+ // Не удалось найти аккаунт
+
+ throw new exception('Не удалось найти аккаунт');
+ }
+
+ if (password_verify($password, $account['password'])) {
+ // Совпадают хеши паролей
+
+ // Инициализация идентификатора сессии
+ session_id($account['id']);
+
+ // Инициализация названия сессии
+ session_name('id');
+
+ // Инициализация сессии
+ session_start();
+
+ // Инициализация времени хранения хеша
+ $time = time() + ($remember ? 604800 : 86400);
+
+ // Инициализация хеша
+ $hash = static::hash((int) $account['id'], crypt($account['password'], time() . $account['id']), $time, $errors)['hash'];
+
+ // Инициализация cookies
+ setcookie("hash", $hash, $time, path: '/', secure: true);
+
+ return $account;
+ } else {
+ // Не совпадают хеши паролей
+
+ throw new exception('Неправильный пароль');
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return [];
+ }
+
+ /**
+ * Аутентификация
+ *
+ * @param array &$errors Журнал ошибок
+ *
+ * @return bool Удалось ли деаутентифицироваться
+ */
+ public static function deauthentication(array &$errors = []): bool
+ {
+ try {
+ if ($account = static::account($errors)) {
+ // Аутентифицирован пользователь
+
+ // Инициализация запроса
+ $request = static::$db->prepare("UPDATE `accounts` SET `hash` = null, `time` = 0 WHERE `id` = :id");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $account['id'],
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ $request->fetch(pdo::FETCH_ASSOC);
+
+ // Деинициализация cookies
+ setcookie("id", '', 0, path: '/', secure: true);
+ setcookie("hash", '', 0, path: '/', secure: true);
+
+ return true;
+ } else {
+ // Не аутентифицирован пользователь
+
+ // Запись ошибки
+ throw new exception('Не аутентифицирован');
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return false;
+ }
+
+ /**
+ * Прочитать данные аккаунта, если пользователь аутентифицирован
+ *
+ * Можно использовать как проверку на аутентифицированность
+ *
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Аккаунт (если не найден, то пустой массив)
+ *
+ * @todo 1. Сделать в static::read() возможность передачи нескольких параметров и перенести туда непосредственно чтение аккаунта с проверкой хеша
+ */
+ public static function account(array &$errors = []): array
+ {
+ try {
+ if (!empty($_COOKIE['id']) && !empty($_COOKIE['hash'])) {
+ // Аутентифицирован аккаунт (найдены cookie и они хранят значения - подразумевается, что не null или пустое)
+
+ if ($_COOKIE['hash'] === static::hash((int) $_COOKIE['id'], errors: $errors)['hash']) {
+ // Совпадает переданный хеш с тем, что хранится в базе данных
+ } else {
+ // Не совпадает переданный хеш с тем, что хранится в базе данных
+
+ // Генерация ошибки
+ throw new exception('Вы аутентифицированы с другого устройства (не совпадают хеши аутентификации)');
+ }
+
+ // Инициализация запроса
+ $request = static::$db->prepare("SELECT * FROM `accounts` WHERE `id` = :id && `hash` = :hash");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $_COOKIE['id'],
+ ":hash" => $_COOKIE['hash'],
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ if (empty($account = $request->fetch(pdo::FETCH_ASSOC))) {
+ // Не найдена связка идентификатора с хешем
+
+ // Генерация ошибки
+ throw new exception('Не найден пользотватель или время аутентификации истекло');
+ }
+
+ // Чтение разрешений
+ $account['permissions'] = static::permissions((int) $account['id'], $errors);
+
+ return $account;
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return [];
+ }
+
+ /**
+ * Прочитать разрешения аккаунта
+ *
+ * @param int $id Идентификатор аккаунта
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Разрешения аккаунта, если найдены
+ */
+ public static function permissions(int $id, array &$errors = []): array
+ {
+ try {
+ // Инициализация запроса
+ $request = static::$db->prepare("SELECT * FROM `permissions` WHERE `id` = :id");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $id
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ if (empty($response = $request->fetch(pdo::FETCH_ASSOC))) {
+ // Не найдены разрешения
+
+ // Генерация ошибки
+ throw new exception('Не найдены разрешения');
+ }
+
+ // Удаление ненужных данных
+ unset($response['id']);
+
+ return $response;
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return [];
+ }
+
+ /**
+ * Запись пользователя в базу данных
+ *
+ * @param string|null $name Имя
+ * @param string|null $email Почта
+ * @param string|null $password Пароль
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Аккаунт (если не найден, то пустой массив)
+ */
+ public static function write(string|null $name = null, string|null $email = null, string|null $password = null, array &$errors = []): array
+ {
+ try {
+ // Инициализация параметров запроса
+ $params = [];
+
+ if (isset($name)) {
+ try {
+ // Проверка параметра
+ if (iconv_strlen($name) < 3) throw new exception('Длина имени должна быть не менее 3 символов');
+ if (iconv_strlen($name) > 60) throw new exception('Длина имени должна быть не более 60 символов');
+
+ // Запись в буфер параметров запроса
+ $params[':name'] = $name;
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+
+ goto end;
+ }
+ }
+
+ if (isset($email)) {
+ try {
+ // Проверка параметра
+ if (filter_var($email, FILTER_VALIDATE_EMAIL) === false) throw new exception('Не удалось распознать почту');
+ if (iconv_strlen($email) < 3) throw new exception('Длина почты должна быть не менее 3 символов');
+ if (iconv_strlen($email) > 60) throw new exception('Длина почты должна быть не более 80 символов');
+
+ // Запись в буфер параметров запроса
+ $params[':email'] = $email;
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+
+ goto end;
+ }
+ }
+
+ if (isset($password)) {
+ try {
+ // Проверка параметра
+ if (iconv_strlen($password) < 3) throw new exception('Длина пароля должна быть не менее 3 символов');
+ if (iconv_strlen($password) > 60) throw new exception('Длина пароля должна быть не более 120 символов');
+
+ // Запись в буфер параметров запроса
+ $params[':password'] = password_hash($password, PASSWORD_BCRYPT);
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+
+ goto end;
+ }
+ }
+
+ // Инициализация запроса
+ $request = static::$db->prepare("INSERT INTO `accounts` (" . (isset($name) ? '`name`' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? '`email`' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? '`password`' : '') . ") VALUES (" . (isset($name) ? ':name' : '') . (isset($name) && isset($email) ? ', ' : '') . (isset($email) ? ':email' : '') . ((isset($name) || isset($email)) && isset($password) ? ', ' : '') . (isset($password) ? ':password' : '') . ")");
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ $request->fetch(pdo::FETCH_ASSOC);
+
+ try {
+ if (isset($name)) {
+ // Передано имя аккаунта
+
+ // Чтение аккаунта
+ $account = static::read(['name' => $name]);
+ } else if (isset($email)) {
+ // Передана почта аккаунта
+
+ // Чтение аккаунта
+ $account = static::read(['email' => $email]);
+ } else {
+ // Не передано ни имя, ни почта
+
+ throw new exception('Не переданны данные для полноценной регистрации аккаунта');
+ }
+
+ // Инициализация запроса
+ $request = static::$db->prepare("INSERT INTO `permissions` (`id`) VALUES (:id)");
+
+ // Инициализация параметров
+ $params = [
+ ':id' => $account['id']
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ $request->fetch(pdo::FETCH_ASSOC);
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ // Конец выполнения
+ end:
+
+ return isset($account) && $account ? $account : [];
+ }
+
+ /**
+ * Чтение пользователя из базы данных
+ *
+ * @param array $search Поиск ('поле' => 'значение'), работает только с одним полем
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Аккаунт, если найден
+ */
+ public static function read(array $search, array &$errors = []): array
+ {
+ try {
+ // Инициализация данных для поиска
+ $field = array_keys($search)[0] ?? null;
+ $value = $search[$field] ?? null;
+
+ if (empty($field)) {
+ // Получено пустое значение поля
+
+ // Запись ошибки
+ throw new exception('Пустое значение поля для поиска');
+ }
+
+ // Инициализация запроса
+ $request = static::$db->prepare("SELECT * FROM `accounts` WHERE `$field` = :field LIMIT 1");
+
+ // Параметры запроса
+ $params = [
+ ":field" => $value,
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ if ($account = $request->fetch(pdo::FETCH_ASSOC)) {
+ // Найден аккаунт
+
+ try {
+ if ($permissions = static::permissions((int) $account['id'], $errors)) {
+ // Найдены разрешения
+
+ // Запись в буфер данных аккаунта
+ $account['permissions'] = $permissions;
+ } else {
+ // Не найдены разрешения
+
+ throw new exception('Не удалось найти и прочитать разрешения');
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+ } else {
+ // Не найден аккаунт
+
+ throw new exception('Не удалось найти аккаунт');
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return isset($account) && $account ? $account : [];
+ }
+
+ /**
+ * Запись или чтение хеша из базы данных
+ *
+ * @param int $id Идентификатор аккаунта
+ * @param int|null $hash Хеш аутентифиакции
+ * @param string|null $time Время хранения хеша
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array ['hash' => $hash, 'time' => $time]
+ */
+ public static function hash(int $id, string|null $hash = null, int|null $time = null, array &$errors = []): array
+ {
+ try {
+ if (isset($hash, $time)) {
+ // Переданы хеш и его время хранения
+
+ // Инициализация запроса
+ $request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $id,
+ ":hash" => $hash,
+ ":time" => $time,
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ $request->fetch(pdo::FETCH_ASSOC);
+ } else {
+ // Не переданы хеш и его время хранения
+
+ // Инициализация запроса
+ $request = static::$db->prepare("SELECT `hash`, `time` FROM `accounts` WHERE `id` = :id");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $id,
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ extract((array) $request->fetch(pdo::FETCH_ASSOC));
+
+ if (!empty($response['time']) && $response['time'] <= time()) {
+ // Истекло время жизни хеша
+
+ // Инициализация запроса
+ $request = static::$db->prepare("UPDATE `accounts` SET `hash` = :hash, `time` = :time WHERE `id` = :id");
+
+ // Параметры запроса
+ $params = [
+ ":id" => $id,
+ ":hash" => null,
+ ":time" => null,
+ ];
+
+ // Отправка запроса
+ $request->execute($params);
+
+ // Генерация ответа
+ $response = $request->fetch(pdo::FETCH_ASSOC);
+
+ // Генерация ошибки
+ throw new exception('Время аутентификации истекло');
+ }
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return ['hash' => $hash, 'time' => $time];
+ }
+}
diff --git a/mirzaev/calculator/system/models/calculators_model.php b/mirzaev/calculator/system/models/calculators_model.php
new file mode 100644
index 0000000..6745bb1
--- /dev/null
+++ b/mirzaev/calculator/system/models/calculators_model.php
@@ -0,0 +1,186 @@
+
+ */
+final class calculators_model extends core
+{
+ /**
+ * Рассчет калькулятора лазерной резки
+ *
+ * @param bool|int|string|null $company Юридическое лицо? Или физическое лицо?
+ * @param string|null $complexity Сложность ('easy', 'medium', 'hard')
+ * @param int|string|null $width Высота (мм)
+ * @param int|string|null $height Ширина (мм)
+ * @param int|string|null $lenght Длина (мм)
+ * @param int|string|null $amount Количество
+ * @param string|null $metal Тип металла
+ * @param int|string|null $holes Количество отверстий
+ * @param int|string|null $diameter Диаметр отверстий (мм)
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array|bool Аккаунт, если удалось аутентифицироваться
+ *
+ * @todo
+ * 1. Значения по умолчанию брать из настроек в базе данных
+ */
+ public static function laser(
+ bool|int|string|null $company = null,
+ string|null $complexity = null,
+ int|string|null $width = null,
+ int|string|null $height = null,
+ int|string|null $length = null,
+ int|string|null $amount = null,
+ string|null $metal = null,
+ int|string|null $holes = null,
+ int|string|null $diameter = null,
+ array &$errors = []
+ ): array {
+ try {
+ // Инициализация переменных для буфера вывода
+ $machine = $manager = $engineer = $operator = 0;
+
+ // Инициализация значений по умолчанию (см. @todo)
+ $company = (bool) ($company ?? settings::read('default_buyer', $errors) === 'entity' ?? false);
+ $complexity = (string) ($complexity ?? settings::read('default_complexity', $errors) ?? 'medium');
+ $width = (int) ($width ?? settings::read('default_width', $errors) ?? 500);
+ $height = (int) ($height ?? settings::read('default_height', $errors) ?? 500);
+ $length = (int) ($length ?? settings::read('default_length', $errors) ?? 1);
+ $amount = (int) ($amount ?? settings::read('default_amount', $errors) ?? 1);
+ $metal = (string) ($metal ?? settings::read('default_metal', $errors) ?? 'stainless_steel');
+ $holes = (int) ($holes ?? settings::read('default_holes', $errors) ?? 0);
+ $diameter = (int) ($diameter ?? settings::read('default_diameter', $errors) ?? 0);
+
+ // Стоисмость киловатта электроэнергии
+ $electricity = settings::read('electricity', $errors) ?? 6.5;
+
+ // Потребляемая электроэнергия станком (квт/ч)
+ $power = settings::read('laser_power', $errors) ?? 2;
+
+ // 1 мм толщина = 220 мм/с рез у нержавеющей стали
+ $speed = 220;
+
+ // Вычисление площади
+ $area = $width * $height;
+
+ // Коэффициент сложности
+ $coefficient = ($area <= (settings::read('coefficient_area_less', $errors) ?? 10000) || $area >= (settings::read('coefficient_area_more', $errors) ?? 100000) ? (settings::read('coefficient_area_degree', $errors) ?? 0.2) : 0) + match ($complexity) {
+ 'easy' => (settings::read('coefficient_complexity_easy', $errors) ?? 0.8),
+ 'medium' => (settings::read('coefficient_complexity_medium', $errors) ?? 1),
+ 'hard' => (settings::read('coefficient_complexity_hard', $errors) ?? 1.2),
+ default => (settings::read('coefficient_complexity_medium', $errors) ?? 1)
+ };
+
+ // Расчет длины реза (мм)
+ eval(settings::read('laser_formula_cutting', $errors) ?? '$cutting = 3.14 * $diameter * $holes + ($width * 2 + $height * 2) * $coefficient;');
+
+ // Скорость реза в час (мм/с)
+ eval(settings::read('laser_formula_speed', $errors) ?? '$speed = 3600 * $speed;');
+
+ // Стоимость реза в час
+ eval(settings::read('laser_formula_hour', $errors) ?? '$hour = $electricity * $power;');
+
+ // Стоимость 1 миллиметра реза
+ eval(settings::read('laser_formula_millimeter', $errors) ?? '$millimeter = $hour / $speed;');
+
+ // Cтанок (стоимость работы)
+ eval(settings::read('laser_formula_machine', $errors) ?? '$machine = $cutting * $millimeter * $amount;');
+
+ var_dump($company, $complexity, $width, $height, $length, $amount, $metal, $holes, $diameter);
+
+ if ($company) {
+ // Юридическое лицо
+
+ $min = settings::read('manager_entity_min', $errors) ?? 1;
+ $max = settings::read('manager_entity_max', $errors) ?? 7;
+ $average = ($min + $max) / 2;
+
+ // Менеджер (стоимость работы)
+ $manager = (settings::read('manager_entity_hour', $errors) ?? 200) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+
+ $min = settings::read('engineer_entity_min', $errors) ?? 2;
+ $max = settings::read('engineer_entity_max', $errors) ?? 72;
+ $average = ($min + $max) / 2;
+
+ // Инженер (стоимость работы)
+ $engineer = (settings::read('engineer_entity_hour', $errors) ?? 400) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+
+ $min = settings::read('operator_entity_min', $errors) ?? 0.33;
+ $max = settings::read('operator_entity_max', $errors) ?? 16;
+ $average = ($min + $max) / 2;
+
+ // Оператор
+ $operator = (settings::read('operator_entity_hour', $errors) ?? 200) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+ } else {
+ // Физическое лицо
+
+ $min = settings::read('manager_individual_min', $errors) ?? 1;
+ $max = settings::read('manager_individual_max', $errors) ?? 3;
+ $average = ($min + $max) / 2;
+
+ // Менеджер (стоимость работы)
+ $manager = (settings::read('manager_individual_hour', $errors) ?? 200) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+
+ $min = settings::read('manager_individual_min', $errors) ?? 2;
+ $max = settings::read('manager_individual_max', $errors) ?? 10;
+ $average = ($min + $max) / 2;
+
+ // Инженер (стоимость работы)
+ $engineer = (settings::read('engineer_individual_hour', $errors) ?? 300) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+
+ $min = settings::read('manager_individual_min', $errors) ?? 0.33;
+ $max = settings::read('manager_individual_max', $errors) ?? 7;
+ $average = ($min + $max) / 2;
+
+ // Оператор (стоимость работы)
+ $operator = (settings::read('operator_individual_hour', $errors) ?? 200) * match ($complexity) {
+ 'easy' => $min,
+ 'medium' => $average,
+ 'hard' => $max,
+ default => $average
+ };
+ }
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return [[$machine], [$manager], [$engineer], [$operator]];
+ }
+}
diff --git a/mirzaev/calculator/system/models/core.php b/mirzaev/calculator/system/models/core.php
new file mode 100644
index 0000000..4e9d6e7
--- /dev/null
+++ b/mirzaev/calculator/system/models/core.php
@@ -0,0 +1,139 @@
+
+ */
+class core extends model
+{
+ /**
+ * Соединение с базой данных
+ */
+ protected static PDO $db ;
+
+ public function __construct(pdo $db = null)
+ {
+ if (isset($db)) {
+ // Получена инстанция соединения с базой данных
+
+ // Запись и инициализация соединения с базой данных
+ $this->__set('db', $db);
+ } else {
+ // Не получена инстанция соединения с базой данных
+
+ // Инициализация соединения с базой данных по умолчанию
+ $this->__get('db');
+ }
+ }
+
+ /**
+ * Записать свойство
+ *
+ * @param string $name Название
+ * @param mixed $value Значение
+ */
+ public function __set(string $name, mixed $value = null): void
+ {
+ match ($name) {
+ 'db' => (function () use ($value) {
+ if ($this->__isset('db')) {
+ // Свойство уже было инициализировано
+
+ // Выброс исключения (неудача)
+ throw new exception('Запрещено реинициализировать соединение с базой данных ($this->db)', 500);
+ } else {
+ // Свойство ещё не было инициализировано
+
+ if ($value instanceof pdo) {
+ // Передано подходящее значение
+
+ // Запись свойства (успех)
+ self::$db = $value;
+ } else {
+ // Передано неподходящее значение
+
+ // Выброс исключения (неудача)
+ throw new exception('Соединение с базой данных ($this->db) должен быть инстанцией PDO', 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 pdo(\TYPE . ':dbname=' . \BASE . ';host=' . \HOST, LOGIN, PASSWORD));
+ }
+
+ return self::$db;
+ })(),
+ default => parent::__get($name)
+ };
+ }
+
+ /**
+ * Проверить свойство на инициализированность
+ *
+ * @param string $name Название
+ */
+ public function __isset(string $name): bool
+ {
+ return match ($name) {
+ default => parent::__isset($name)
+ };
+ }
+
+ /**
+ * Удалить свойство
+ *
+ * @param string $name Название
+ */
+ public function __unset(string $name): void
+ {
+ match ($name) {
+ default => parent::__isset($name)
+ };
+ }
+
+
+ /**
+ * Статический вызов
+ *
+ * @param string $name Название
+ * @param array $arguments Параметры
+ */
+ public static function __callStatic(string $name, array $arguments): mixed
+ {
+ match ($name) {
+ 'db' => (new static)->__get('db'),
+ default => throw new exception("Не найдено свойство или функция: $name", 500)
+ };
+ }
+}
diff --git a/mirzaev/calculator/system/models/settings_model.php b/mirzaev/calculator/system/models/settings_model.php
new file mode 100644
index 0000000..ba0d3f5
--- /dev/null
+++ b/mirzaev/calculator/system/models/settings_model.php
@@ -0,0 +1,44 @@
+
+ */
+final class settings_model extends core
+{
+ /**
+ * Прочитать
+ *
+ * @param string $name Название параметра
+ * @param array &$errors Журнал ошибок
+ *
+ * @return array Аккаунт, если найден
+ */
+ public static function read(string $name, array &$errors = []): int|float|string|array|null
+ {
+ try {
+ // Инициализация запроса
+ $request = static::$db->prepare("SELECT `$name` FROM `settings` WHERE `id` = 1 ORDER BY `id` DESC LIMIT 1");
+
+ // Отправка запроса
+ $request->execute();
+
+ // Генерация ответа
+ return $request->fetchColumn();
+ } catch (exception $e) {
+ // Запись в журнал ошибок
+ $errors[] = $e->getMessage();
+ }
+
+ return null;
+ }
+}
diff --git a/mirzaev/calculator/system/public/css/auth.css b/mirzaev/calculator/system/public/css/auth.css
index 3b25bd6..cbbc18e 100644
--- a/mirzaev/calculator/system/public/css/auth.css
+++ b/mirzaev/calculator/system/public/css/auth.css
@@ -1,8 +1,21 @@
-#authentication>form {
+#authentication>:is(form, div) {
display: flex;
flex-direction: column;
}
+#authentication .exit {
+ margin-top: 25px;
+}
+
+#authentication p {
+ margin: 0;
+ display: flex;
+}
+
+#authentication p>span {
+ margin-left: auto;
+}
+
#authentication>form>input:is([type="text"], [type="password"]) {
margin-bottom: 12px;
}
@@ -13,6 +26,7 @@
#authentication>form>.submit {
margin-top: 6px;
+ margin-bottom: 10px;
display: flex;
}
@@ -28,3 +42,26 @@
border: unset;
border-radius: 0 3px 3px 0;
}
+
+
+#authentication>form>input[type=submit].registration {
+ padding: 7px 20px;
+ background-color: #86781C;
+}
+
+#authentication>form>input[type=submit].registration:hover {
+ background-color: #9e8d20;
+}
+
+#authentication>form>input[type=submit].registration:is(:active, :focus) {
+ background-color: #776b19;
+}
+
+#authentication>form>ul.errors {
+ margin-top: 18px;
+ margin-bottom: 0px;
+ padding: 10px;
+ text-align: center;
+ list-style: none;
+ background-color: #ae8f8f;
+}
diff --git a/mirzaev/calculator/system/public/css/calculator.css b/mirzaev/calculator/system/public/css/calculator.css
index 785b809..3b1c1a1 100644
--- a/mirzaev/calculator/system/public/css/calculator.css
+++ b/mirzaev/calculator/system/public/css/calculator.css
@@ -32,26 +32,49 @@
margin: 8px auto 15px;
}
-#calculator>#result>:last-child {
+#calculator>#result>div {
width: 30%;
}
+#calculator>#result>nav {
+ margin-right: 30px;
+ width: 100%;
+ display: flex;
+}
+
+#calculator>#result>nav>a {
+ margin-top: auto;
+}
+#calculator>#result>nav>a#calculate {
+ padding: 10px 5px;
+}
+
#calculator>#result,
-#calculator>#result :last-child>p {
+#calculator>#result>div>p {
font-weight: bold;
display: flex;
}
-#calculator>#result :last-child>p * {
+#calculator>#result>div>p * {
font-weight: normal;
}
-#calculator>#result>:last-child,
-#calculator>#result :last-child>p>:is(#expenses, #income, #profit) {
+#calculator>#result>div,
+#calculator>#result>div>p>:is(#expenses, #income, #profit) {
margin-left: auto;
}
-#calculator>#result :last-child>p>:last-child {
+#calculator>#result>nav>a:first-child:not(:only-of-type),
+#calculator>#result>div>p:first-child:not(:only-of-type) {
+ margin-top: 0;
+}
+
+#calculator>#result>nav>a:last-child,
+#calculator>#result>div>p:last-child {
+ margin-bottom: 0;
+}
+
+#calculator>#result>div>p>:last-child {
margin-left: 4px;
}
@@ -59,58 +82,58 @@
margin-top: 30px;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting']) {
+#calculator>.calculator {
padding: 5px 30px 10px 30px;
display: flex;
flex-direction: column;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div {
+#calculator>.calculator>div {
display: flex;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>label {
+#calculator>.calculator>div>label {
font-weight: bold;
margin: auto 15px auto auto;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div:not(:last-child) {
+#calculator>.calculator>div:not(:last-child) {
margin-bottom: 15px;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div {
+#calculator>.calculator>div>div {
width: 60%;
display: flex;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div {
+#calculator>.calculator>div>div {
height: 30px;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>:is(input, small, span):not(.measured, :last-child) {
+#calculator>.calculator>div>div>:is(input, small, span):not(.measured, :last-child) {
margin-right: 5px !important;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>:is(input, .unit) {
+#calculator>.calculator>div>div>:is(input, .unit) {
font-size: small;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input {
+#calculator>.calculator>div>div>input {
width: 25px;
padding: 5px 10px;
text-align: center;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input.measured {
+#calculator>.calculator>div>div>input.measured {
padding-right: 3px;
text-align: right;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>small {
+#calculator>.calculator>div>div>small {
margin: auto 0;
}
-#calculator>:is([id^='laser'], [id^='plasma'], [id^='bending'], [id^='painting'])>div>div>input+.unit {
+#calculator>.calculator>div>div>input+.unit {
margin: auto 0;
padding-top: unset;
padding-bottom: unset;
diff --git a/mirzaev/calculator/system/public/css/main.css b/mirzaev/calculator/system/public/css/main.css
index 9971db7..767a884 100644
--- a/mirzaev/calculator/system/public/css/main.css
+++ b/mirzaev/calculator/system/public/css/main.css
@@ -1,5 +1,6 @@
* {
font-family: "open sans";
+ text-decoration: none;
}
body {
@@ -69,7 +70,7 @@ header>nav {
top: 0;
width: 100%;
height: 40px;
- padding: 8px calc(18.5% - 20px);
+ padding: 8px 20%;
position: sticky;
display: flex;
pointer-events: all;
diff --git a/mirzaev/calculator/system/public/img/black.svg b/mirzaev/calculator/system/public/img/black.svg
new file mode 100644
index 0000000..3533c93
--- /dev/null
+++ b/mirzaev/calculator/system/public/img/black.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mirzaev/calculator/system/public/img/white.svg b/mirzaev/calculator/system/public/img/white.svg
new file mode 100644
index 0000000..719c4a0
--- /dev/null
+++ b/mirzaev/calculator/system/public/img/white.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/mirzaev/calculator/system/public/index.php b/mirzaev/calculator/system/public/index.php
index 0073070..9760d38 100644
--- a/mirzaev/calculator/system/public/index.php
+++ b/mirzaev/calculator/system/public/index.php
@@ -8,6 +8,11 @@ use mirzaev\minimal\core;
use mirzaev\minimal\router;
define('VIEWS', realpath('..' . DIRECTORY_SEPARATOR . 'views'));
+define('TYPE', 'mysql');
+define('BASE', 'calculator');
+define('HOST', '127.0.0.1');
+define('LOGIN', 'root');
+define('PASSWORD', '');
// Автозагрузка
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
@@ -17,13 +22,22 @@ $router = new router;
// Запись маршрутов
$router->write('/', 'main', 'index');
-$router->write('/calculator', 'calculator', 'index');
-$router->write('/calculator/generate/buyer', 'calculator', 'buyer');
-$router->write('/calculator/generate/complexity', 'calculator', 'complexity');
-$router->write('/calculator/generate/menu', 'calculator', 'menu');
-$router->write('/calculator/generate/result', 'calculator', 'result');
-$router->write('/calculator/generate/divider', 'calculator', 'divider');
-$router->write('/calculator/generate/laser', 'calculator', 'laser');
+$router->write('/account/registration', 'accounts', 'registration', 'POST');
+$router->write('/account/authentication', 'accounts', 'authentication', 'POST');
+$router->write('/account/deauthentication', 'accounts', 'deauthentication', 'POST');
+$router->write('/account/deauthentication', 'accounts', 'deauthentication', 'GET');
+$router->write('/account/data', 'accounts', 'data', 'POST');
+$router->write('/calculator', 'calculator', 'index', 'POST');
+$router->write('/calculator/generate/buyer', 'calculator', 'buyer', 'POST');
+$router->write('/calculator/generate/complexity', 'calculator', 'complexity', 'POST');
+$router->write('/calculator/generate/menu', 'calculator', 'menu', 'POST');
+$router->write('/calculator/generate/result', 'calculator', 'result', 'POST');
+$router->write('/calculator/generate/divider', 'calculator', 'divider', 'POST');
+$router->write('/calculator/generate/laser', 'calculator', 'laser', 'POST');
+$router->write('/calculator/calculate', 'calculator', 'calculate', 'POST');
+$router->write('/settings', 'settings', 'index', 'GET');
+$router->write('/settings/write', 'settings', 'write', 'POST');
+$router->write('/settings/read', 'settings', 'read', 'POST');
// Инициализация ядра
$core = new Core(namespace: __NAMESPACE__, router: $router);
diff --git a/mirzaev/calculator/system/public/js/auth.js b/mirzaev/calculator/system/public/js/auth.js
new file mode 100644
index 0000000..1e72fc8
--- /dev/null
+++ b/mirzaev/calculator/system/public/js/auth.js
@@ -0,0 +1,37 @@
+'use strict';
+
+function remember_switch(target) {
+ if (target.classList.contains('fa-unlock')) {
+ // Найден "открытый замок"
+
+ // Перезапись на "закрытый замок"
+ target.classList.remove('fa-unlock');
+ target.classList.add('fa-lock');
+
+ // Изменение отправляемого значения
+ document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = true;
+ } else {
+ // Не найден "открытый замок", подразумевается, что найден "закрытый замок"
+
+ // Перезапись на "открытый замок"
+ target.classList.remove('fa-lock');
+ target.classList.add('fa-unlock');
+
+ // Изменение отправляемого значения
+ document.querySelector('input[name=' + target.getAttribute('for') + ']').checked = false;
+ }
+}
+
+function authentication(form) {
+ // Инициализация адреса отправки формы
+ form.action = '/account/authentication';
+
+ return true;
+}
+
+function registration(form) {
+ // Инициализация адреса отправки формы
+ form.action = '/account/registration';
+
+ return true;
+}
diff --git a/mirzaev/calculator/system/public/js/calculator.js b/mirzaev/calculator/system/public/js/calculator.js
index cd7a1a9..ac92008 100644
--- a/mirzaev/calculator/system/public/js/calculator.js
+++ b/mirzaev/calculator/system/public/js/calculator.js
@@ -3,32 +3,132 @@
let calculator = {
index: document.getElementById("calculator"),
calculators: [],
+ account: [],
settings: {
defaults: {
buyer: 'individual',
- complexity: 'hard',
+ complexity: 'medium',
}
},
init() {
// Инициализация калькулятора
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ
+ // !!!!!!!!!!!!!!!!! РАЗОБРАТЬСЯ С ПОРЯДКОМ ВЫПОЛНЕНИЯ
- this.generate.buyer(this.settings.defaults.buyer);
- setTimeout(() => {
- this.generate.complexity(this.settings.defaults.complexity);
- setTimeout(() => {
- this.generate.menu();
- setTimeout(() => {
- // this.calculate();
- this.generate.result();
- }, 100);
- }, 100);
- }, 100);
+ this.generate.buyer(this.settings.defaults.buyer)
+ .then(this.generate.complexity(this.settings.defaults.complexity)
+ .then(this.generate.menu()
+ .then(this.authenticate(cookie.read('id'))
+ .then(success => {
+ // Запись данных аккаунта
+ this.account = success;
+
+ if (this.account !== undefined && typeof this.account === 'object' && this.account.permissions !== undefined) {
+ // Найден аккаунт
+
+ if (this.account.permissions.calculate == 1) {
+ // Разрешено использовать калькулятор
+
+ this.generate.result();
+ }
+ }
+ }
+ )
+ )
+ )
+ );
console.log('[КАЛЬКУЛЯТОР] Инициализирован');
},
- calculate(repeated = false) {
+ authenticate(id) {
+ // Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)'
+
+ return fetch('/account/data?id=' + id, {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ return response.json().then(
+ success => {
+ console.log('[КАЛЬКУЛЯТОР] Загружены данные пользователя: ' + id);
+
+ return success;
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить данные пользователя: ' + id);
+ });
+ }
+ });
+ },
+ calculate() {
+ // Запрос и генерация HTML с данными о рассчете со всех калькуляторов
+
+ // Инициализация буферов вывода
+ let expenses, income, profit;
+
+ // Инициализация буфера запроса
+ let query = {};
+
+ for (const number in this.calculators) {
+ // Перебор калькуляторов
+
+ // Инициализация буфера запроса для нового калькулятора
+ query[number] = {};
+
+ // Инициализация типа калькулятора
+ query[number]['type'] = this.calculators[number].getAttribute('data-calculator');
+
+ for (const buyer of this.index.querySelectorAll('input[name="buyer"]')) {
+ // Перебор полей с параметрами типа заказчика
+
+ if (buyer.checked) {
+ // Найдено выбранное поле
+
+ // Запись в буфер запроса
+ query[number]['buyer'] = buyer.value;
+ }
+ }
+
+ for (const complexity of this.index.querySelectorAll('input[name="complexity"]')) {
+ // Перебор полей с параметрами сложности
+
+ if (complexity.checked) {
+ // Найдено выбранное поле
+
+ // Запись в буфер запроса
+ query[number]['complexity'] = complexity.value;
+ }
+ }
+
+ for (const field of this.calculators[number].querySelectorAll('[data-calculator-parameter]')) {
+ // Перебор полей с параметрами
+
+ // Запись в буфер запроса
+ query[number][field.getAttribute('data-calculator-parameter')] = field.value ?? field.options[field.selectedIndex].text;
+ }
+
+ // Сортировка
+ query[number] = this.sort[query[number]['type']](query[number]);
+ }
+
+ fetch('/calculator/calculate', {
+ method: "POST",
+ headers: { "content-type": "application/json" },
+ body: JSON.stringify(query)
+ }).then((response) => {
+ if (response.status === 200) {
+ return response.json().then(
+ success => {
+ console.log('[КАЛЬКУЛЯТОР] Сгенерирован результат');
+
+ return success;
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось сгенерировать результат');
+ });
+ }
+ });
+
let result = document.getElementById("result");
if (result === null) {
@@ -36,121 +136,118 @@ let calculator = {
// Инициализия
this.generate.result();
-
- if (repeated === false) {
- // Это первое выполнение функции в потенциальном цикле
-
- // Повтор операции
- this.calculate(true);
- }
}
},
generate: {
buyer(value = 'individual') {
// Запрос и генерация HTML с данными о типе покупателя (юр. лицо и физ. лицо)
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/buyer?value=' + value, {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ calculator.index.insertAdjacentHTML('beforeend', success);
- request.open('GET', '/calculator/generate/buyer?value=' + value);
-
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
-
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
-
- console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
+ console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора типа покупателя');
+ });
}
});
-
- request.send();
},
complexity(value = 'medium') {
// Запрос и генерация HTML с данными о сложности работы
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/complexity?value=' + value, {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ calculator.index.insertAdjacentHTML('beforeend', success);
- request.open('GET', '/calculator/generate/complexity?value=' + value);
-
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
-
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
-
- console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
+ console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора сложности');
+ });
}
});
-
- request.send();
},
menu() {
// Запрос и генерация HTML с кнопками добавления калькулятора
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/menu', {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ calculator.index.insertAdjacentHTML('beforeend', success);
- request.open('GET', '/calculator/generate/menu');
-
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
-
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
-
- console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
+ console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками добавления калькулятора');
+ });
}
});
-
- request.send();
},
result() {
// Запрос и генерация HTML с данными о результате калькуляции
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/result', {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ calculator.index.insertAdjacentHTML('beforeend', success);
- request.open('GET', '/calculator/generate/result');
-
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
-
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
-
- console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
+ console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с данными о результате калькуляции');
+ });
}
});
-
- request.send();
},
divider(element, position) {
// Запрос и генерация HTML с данными о результате калькуляции
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/divider', {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ if (element === undefined || position === undefined) {
+ // Не задан элемент и позиция добавляемого разделителя
- request.open('GET', '/calculator/generate/divider');
+ // Запись разделителя в конце калькулятора
+ calculator.index.insertAdjacentHTML('beforeend', success);
+ } else {
+ // Задан элемент и позиция добавляемого разделителя
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
+ // Запись разделителя по заданным параметрам
+ element.insertAdjacentHTML(position, success);
+ }
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
-
- if (element === undefined || position === undefined) {
- // Не задан элемент и позиция добавляемого разделителя
-
- // Запись разделителя в конце калькулятора
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
- } else {
- // Задан элемент и позиция добавляемого разделителя
-
- // Запись разделителя по заданным параметрам
- element.insertAdjacentHTML(position, request.responseText);
- }
-
- console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
+ console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
+ },
+ error => {
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить разделитель');
+ });
}
});
-
- request.send();
},
calculators: {
laser() {
@@ -158,100 +255,133 @@ let calculator = {
// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ
- const request = new XMLHttpRequest();
+ return fetch('/calculator/generate/laser', {
+ method: "POST",
+ headers: { "content-type": "application/x-www-form-urlencoded" }
+ }).then((response) => {
+ if (response.status === 200) {
+ response.text().then(
+ success => {
+ // Поиск последнего калькулятора
+ let last = calculator.calculators[calculator.calculators.length - 1];
- request.open('GET', '/calculator/generate/laser');
-
- request.setRequestHeader('Content-Type', 'application/x-www-form-url');
-
- request.addEventListener("readystatechange", () => {
- if (request.readyState === 4 && request.status === 200) {
- // Поиск последнего калькулятора
- let last = calculator.calculators[calculator.calculators.length - 1];
-
- if (last !== undefined && last !== null) {
- // Найден калькулятор
-
- // Запись калькулятора после последнего калькулятора
- last.insertAdjacentHTML('afterend', request.responseText);
-
- setTimeout(() => {
- // Инициализация разделителя перед меню
- calculator.generate.divider(last, 'afterend');
- }, 100);
- } else {
- // Не найден калькулятор
-
- // Поиск меню
- let menu = document.getElementById("menu");
-
- if (menu !== null) {
- // Найдено меню
-
- // Инициализация разделителя перед меню
- calculator.generate.divider(menu, 'beforebegin');
-
- setTimeout(() => {
- // Запись калькулятора перед меню
- menu.insertAdjacentHTML('beforebegin', request.responseText);
- }, 100);
- } else {
- // Не найдено меню
-
- // Поиск результатов калькуляции
- let result = document.getElementById("result");
-
- if (result !== null) {
- // Найден элемент с результатами калькуляции
+ if (last !== undefined && last !== null) {
+ // Найден калькулятор
// Инициализация разделителя перед меню
- calculator.generate.divider(result, 'beforebegin');
+ calculator.generate.divider(last, 'afterend').then(
+ divider => {
+ // Запись калькулятора после последнего калькулятора
+ last.insertAdjacentHTML('afterend', success);
- setTimeout(() => {
- // Запись калькулятора перед элементом с результатами калькуляции
- result.insertAdjacentHTML('beforebegin', request.responseText);
- }, 100);
+ // Поиск калькуляторов
+ let calculators = calculator.index.querySelectorAll('section[data-calculator]');
+
+ // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
+ calculator.calculators.push(calculators[calculators.length - 1]);
+
+ // Запись в журнал
+ console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
+ }
+ );
} else {
- // Не найден элемент с результатами калькуляции
+ // Не найден калькулятор
- // Инициализация разделителя перед меню
- calculator.generate.divider();
+ // Поиск меню
+ let menu = document.getElementById("menu");
- setTimeout(() => {
- // Запись калькулятора в конце калькулятора
- calculator.index.insertAdjacentHTML('beforeend', request.responseText);
- }, 100);
+ if (menu !== null) {
+ // Найдено меню
+
+ // Инициализация разделителя перед меню
+ calculator.generate.divider(menu, 'beforebegin').then(
+ divider => {
+ // Запись калькулятора перед меню
+ menu.insertAdjacentHTML('beforebegin', success);
+
+ // Поиск калькуляторов
+ let calculators = calculator.index.querySelectorAll('section[data-calculator]');
+
+ // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
+ calculator.calculators.push(calculators[calculators.length - 1]);
+
+ // Запись в журнал
+ console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
+ }
+ );
+ } else {
+ // Не найдено меню
+
+ // Поиск результатов калькуляции
+ let result = document.getElementById("result");
+
+ if (result !== null) {
+ // Найден элемент с результатами калькуляции
+
+ // Инициализация разделителя перед меню
+ calculator.generate.divider(result, 'beforebegin').then(
+ divider => {
+ // Запись калькулятора перед элементом с результатами калькуляции
+ result.insertAdjacentHTML('beforebegin', success);
+
+ // Поиск калькуляторов
+ let calculators = calculator.index.querySelectorAll('section[data-calculator]');
+
+ // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
+ calculator.calculators.push(calculators[calculators.length - 1]);
+
+ // Запись в журнал
+ console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
+ }
+ );
+ } else {
+ // Не найден элемент с результатами калькуляции
+
+ // Инициализация разделителя перед меню
+ calculator.generate.divider().then(
+ divider => {
+ // Запись калькулятора в конце калькулятора
+ calculator.index.insertAdjacentHTML('beforeend', success);
+
+ // Поиск калькуляторов
+ let calculators = calculator.index.querySelectorAll('section[data-calculator]');
+
+ // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
+ calculator.calculators.push(calculators[calculators.length - 1]);
+
+ // Запись в журнал
+ console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
+ }
+ );
+ }
+ }
}
- }
- }
-
- // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ
-
- setTimeout(() => {
- // Поиск только что созданного калькулятора
- let laser = document.getElementById('laser');
-
- if (laser !== null) {
- // Найден только что инициализированный (подразумевается) калькулятор лазерной резки
-
- // Реинициализация идентификатора
- laser.id = 'laser_' + calculator.calculators.length;
-
- // Запись калькулятора в реестр
- calculator.calculators.push(laser);
-
- console.log('[КАЛЬКУЛЯТОР] Загружен калькулятор лазерной резки');
- } else {
- // Не найден только что инициализированный (подразумевается) калькулятор лазерной резки
-
- console.log('[КАЛЬКУЛЯТОР] Не удалось инициализировать калькулятор лазерной резки');
- }
- }, 100);
+ },
+ error => {
+ // Запись в журнал
+ console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось инициализировать калькулятор лазерной резки');
+ });
}
});
-
- request.send();
}
}
+ },
+ sort: {
+ laser(parameters) {
+ // Сортировка параметров для отправки на сервер (динамически вызывается функция-обработчик)
+
+ return {
+ type: 'laser',
+ buyer: parameters['buyer'] ?? null,
+ complexity: parameters['complexity'] ?? null,
+ width: parameters['width'] ?? null,
+ height: parameters['width'] ?? null,
+ length: parameters['length'] ?? null,
+ amount: parameters['amount'] ?? null,
+ metal: parameters['metal'] ?? null,
+ holes: parameters['holes'] ?? null,
+ diameter: parameters['diameter'] ?? null,
+ };
+ }
}
};
diff --git a/mirzaev/calculator/system/public/js/cookie.js b/mirzaev/calculator/system/public/js/cookie.js
new file mode 100644
index 0000000..d241cf7
--- /dev/null
+++ b/mirzaev/calculator/system/public/js/cookie.js
@@ -0,0 +1,55 @@
+'use strict';
+
+let cookie = {
+ read(name) {
+ // Поиск по регулярному выражению
+ let matches = document.cookie.match(new RegExp(
+ "(?:^|; )" + name.replace(/([\.$?*|{}\(\)\[\]\\\/\+^])/g, '\\$1') + "=([^;]*)"
+ ));
+
+ return matches ? decodeURIComponent(matches[1]) : undefined;
+ },
+ write(name, value, options = {}) {
+ // Инициализация параметров
+ options = {
+ path: '/',
+ ...options
+ };
+
+ if (options.expires instanceof Date) {
+ // Передана инстанция Date
+
+ // Запись параметра истечения срока
+ options.expires = options.expires.toUTCString();
+ }
+
+ // Инициализация cookie
+ let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value);
+
+ for (let optionKey in options) {
+ // Перебор параметров
+
+ // Запись в cookie названия параметра
+ updatedCookie += "; " + optionKey;
+
+ // Инициализация значения параметра
+ let optionValue = options[optionKey];
+
+ if (optionValue !== true) {
+ // Найдено значение параметра
+
+ // Запись в cookie значения параметра
+ updatedCookie += "=" + optionValue;
+ }
+ }
+
+ // Конкатенация нового cookie с остальными
+ document.cookie = updatedCookie;
+ },
+ delete(name) {
+ // Удаление
+ setCookie(name, "", {
+ 'max-age': -1
+ })
+ }
+};
diff --git a/mirzaev/calculator/system/views/auth.html b/mirzaev/calculator/system/views/auth.html
index 40eddf7..972f8fe 100644
--- a/mirzaev/calculator/system/views/auth.html
+++ b/mirzaev/calculator/system/views/auth.html
@@ -1,26 +1,39 @@
Почта: {{ account.email }}Аккаунт
+ Аутентификация
-
+ {% endif %}
+