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 @@
+ {% if account is not empty %} +

Аккаунт

+
+

Почта: {{ account.email }}

+ Выход +
+ {% else %}

Аутентификация

-
- - - - - + + +
- - - - + + +
+ - {% if errorMessage is defined %} -

{{ errorMessage }}

+ {% if errors is not empty %} + {% if errors.account is not empty %} + + {% endif %} {% endif %}
+ {% endif %} +
+ + diff --git a/mirzaev/calculator/system/views/calculators/index.html b/mirzaev/calculator/system/views/calculators/index.html index ecd8ac5..0cb8d49 100644 --- a/mirzaev/calculator/system/views/calculators/index.html +++ b/mirzaev/calculator/system/views/calculators/index.html @@ -5,6 +5,7 @@
+