forked from mirzaev/site-tordv-calculator
		
	Панель настроек, асинхронные запросы, калькулятор лазерной резки
This commit is contained in:
		
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
./vendor
 | 
			
		||||
							
								
								
									
										157
									
								
								mirzaev/calculator/system/controllers/accounts_controller.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								mirzaev/calculator/system/controllers/accounts_controller.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,157 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\controllers;
 | 
			
		||||
 | 
			
		||||
use mirzaev\calculator\controllers\core;
 | 
			
		||||
 | 
			
		||||
use mirzaev\calculator\models\accounts_model as accounts;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Контроллер пользователей
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\controllers
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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
 | 
			
		||||
        ]);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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)';
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -0,0 +1,91 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\controllers;
 | 
			
		||||
 | 
			
		||||
use mirzaev\calculator\controllers\core;
 | 
			
		||||
use mirzaev\calculator\models\accounts_model as accounts;
 | 
			
		||||
use mirzaev\calculator\models\settings_model as settings;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Контроллер страницы настроек
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\controllers
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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);
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										531
									
								
								mirzaev/calculator/system/models/accounts_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										531
									
								
								mirzaev/calculator/system/models/accounts_model.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,531 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\models;
 | 
			
		||||
 | 
			
		||||
use pdo;
 | 
			
		||||
use exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Модель регистрации, аутентификации и авторизации
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\models
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										186
									
								
								mirzaev/calculator/system/models/calculators_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								mirzaev/calculator/system/models/calculators_model.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,186 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\models;
 | 
			
		||||
 | 
			
		||||
use mirzaev\calculator\models\settings_model as settings;
 | 
			
		||||
 | 
			
		||||
use exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Модель калькуляторов
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\models
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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]];
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										139
									
								
								mirzaev/calculator/system/models/core.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								mirzaev/calculator/system/models/core.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,139 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\models;
 | 
			
		||||
 | 
			
		||||
use mirzaev\minimal\model;
 | 
			
		||||
 | 
			
		||||
use pdo;
 | 
			
		||||
use pdoexception;
 | 
			
		||||
 | 
			
		||||
use exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Ядро моделей
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\models
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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)
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										44
									
								
								mirzaev/calculator/system/models/settings_model.php
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								mirzaev/calculator/system/models/settings_model.php
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
			
		||||
<?php
 | 
			
		||||
 | 
			
		||||
declare(strict_types=1);
 | 
			
		||||
 | 
			
		||||
namespace mirzaev\calculator\models;
 | 
			
		||||
 | 
			
		||||
use pdo;
 | 
			
		||||
use exception;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Модель настроек
 | 
			
		||||
 *
 | 
			
		||||
 * @package mirzaev\calculator\models
 | 
			
		||||
 * @author Arsen Mirzaev Tatyano-Muradovich <arsen@mirzaev.sexy>
 | 
			
		||||
 */
 | 
			
		||||
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;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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;
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								mirzaev/calculator/system/public/img/black.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mirzaev/calculator/system/public/img/black.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 64 KiB  | 
							
								
								
									
										1
									
								
								mirzaev/calculator/system/public/img/white.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								mirzaev/calculator/system/public/img/white.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
		 After Width: | Height: | Size: 64 KiB  | 
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								mirzaev/calculator/system/public/js/auth.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								mirzaev/calculator/system/public/js/auth.js
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
}
 | 
			
		||||
@@ -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)
 | 
			
		||||
            .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.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);
 | 
			
		||||
                                }
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        )
 | 
			
		||||
                    )
 | 
			
		||||
                )
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
        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();
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора типа покупателя');
 | 
			
		||||
                        },
 | 
			
		||||
                        error => {
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора типа покупателя');
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            request.send();
 | 
			
		||||
        },
 | 
			
		||||
        complexity(value = 'medium') {
 | 
			
		||||
            // Запрос и генерация HTML с данными о сложности работы
 | 
			
		||||
 | 
			
		||||
            const request = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками выбора сложности');
 | 
			
		||||
                        },
 | 
			
		||||
                        error => {
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками выбора сложности');
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            request.send();
 | 
			
		||||
        },
 | 
			
		||||
        menu() {
 | 
			
		||||
            // Запрос и генерация HTML с кнопками добавления калькулятора
 | 
			
		||||
 | 
			
		||||
            const request = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] Загружен элемент с кнопками добавления калькулятора');
 | 
			
		||||
                        },
 | 
			
		||||
                        error => {
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с кнопками добавления калькулятора');
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            request.send();
 | 
			
		||||
        },
 | 
			
		||||
        result() {
 | 
			
		||||
            // Запрос и генерация HTML с данными о результате калькуляции
 | 
			
		||||
 | 
			
		||||
            const request = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
            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);
 | 
			
		||||
            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);
 | 
			
		||||
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] Загружен элемент с данными о результате калькуляции');
 | 
			
		||||
                        },
 | 
			
		||||
                        error => {
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить элемент с данными о результате калькуляции');
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            request.send();
 | 
			
		||||
        },
 | 
			
		||||
        divider(element, position) {
 | 
			
		||||
            // Запрос и генерация HTML с данными о результате калькуляции
 | 
			
		||||
 | 
			
		||||
            const request = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
            request.open('GET', '/calculator/generate/divider');
 | 
			
		||||
 | 
			
		||||
            request.setRequestHeader('Content-Type', 'application/x-www-form-url');
 | 
			
		||||
 | 
			
		||||
            request.addEventListener("readystatechange", () => {
 | 
			
		||||
                if (request.readyState === 4 && request.status === 200) {
 | 
			
		||||
 | 
			
		||||
            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) {
 | 
			
		||||
                                // Не задан элемент и позиция добавляемого разделителя
 | 
			
		||||
 | 
			
		||||
                                // Запись разделителя в конце калькулятора
 | 
			
		||||
                        calculator.index.insertAdjacentHTML('beforeend', request.responseText);
 | 
			
		||||
                                calculator.index.insertAdjacentHTML('beforeend', success);
 | 
			
		||||
                            } else {
 | 
			
		||||
                                // Задан элемент и позиция добавляемого разделителя
 | 
			
		||||
 | 
			
		||||
                                // Запись разделителя по заданным параметрам
 | 
			
		||||
                        element.insertAdjacentHTML(position, request.responseText);
 | 
			
		||||
                                element.insertAdjacentHTML(position, success);
 | 
			
		||||
                            }
 | 
			
		||||
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] Загружен разделитель');
 | 
			
		||||
                        },
 | 
			
		||||
                        error => {
 | 
			
		||||
                            console.log('[КАЛЬКУЛЯТОР] [ОШИБКА] Не удалось загрузить разделитель');
 | 
			
		||||
                        });
 | 
			
		||||
                }
 | 
			
		||||
            });
 | 
			
		||||
 | 
			
		||||
            request.send();
 | 
			
		||||
        },
 | 
			
		||||
        calculators: {
 | 
			
		||||
            laser() {
 | 
			
		||||
@@ -158,27 +255,35 @@ let calculator = {
 | 
			
		||||
 | 
			
		||||
                // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! ИЗБАВИТЬСЯ ОТ ТАЙМАУТОВ
 | 
			
		||||
 | 
			
		||||
                const request = new XMLHttpRequest();
 | 
			
		||||
 | 
			
		||||
                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) {
 | 
			
		||||
                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];
 | 
			
		||||
 | 
			
		||||
                                if (last !== undefined && last !== null) {
 | 
			
		||||
                                    // Найден калькулятор
 | 
			
		||||
 | 
			
		||||
                            // Запись калькулятора после последнего калькулятора
 | 
			
		||||
                            last.insertAdjacentHTML('afterend', request.responseText);
 | 
			
		||||
 | 
			
		||||
                            setTimeout(() => {
 | 
			
		||||
                                    // Инициализация разделителя перед меню
 | 
			
		||||
                                calculator.generate.divider(last, 'afterend');
 | 
			
		||||
                            }, 100);
 | 
			
		||||
                                    calculator.generate.divider(last, 'afterend').then(
 | 
			
		||||
                                        divider => {
 | 
			
		||||
                                            // Запись калькулятора после последнего калькулятора
 | 
			
		||||
                                            last.insertAdjacentHTML('afterend', success);
 | 
			
		||||
 | 
			
		||||
                                            // Поиск калькуляторов
 | 
			
		||||
                                            let calculators = calculator.index.querySelectorAll('section[data-calculator]');
 | 
			
		||||
 | 
			
		||||
                                            // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
 | 
			
		||||
                                            calculator.calculators.push(calculators[calculators.length - 1]);
 | 
			
		||||
 | 
			
		||||
                                            // Запись в журнал
 | 
			
		||||
                                            console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
 | 
			
		||||
                                        }
 | 
			
		||||
                                    );
 | 
			
		||||
                                } else {
 | 
			
		||||
                                    // Не найден калькулятор
 | 
			
		||||
 | 
			
		||||
@@ -189,12 +294,21 @@ let calculator = {
 | 
			
		||||
                                        // Найдено меню
 | 
			
		||||
 | 
			
		||||
                                        // Инициализация разделителя перед меню
 | 
			
		||||
                                calculator.generate.divider(menu, 'beforebegin');
 | 
			
		||||
 | 
			
		||||
                                setTimeout(() => {
 | 
			
		||||
                                        calculator.generate.divider(menu, 'beforebegin').then(
 | 
			
		||||
                                            divider => {
 | 
			
		||||
                                                // Запись калькулятора перед меню
 | 
			
		||||
                                    menu.insertAdjacentHTML('beforebegin', request.responseText);
 | 
			
		||||
                                }, 100);
 | 
			
		||||
                                                menu.insertAdjacentHTML('beforebegin', success);
 | 
			
		||||
 | 
			
		||||
                                                // Поиск калькуляторов
 | 
			
		||||
                                                let calculators = calculator.index.querySelectorAll('section[data-calculator]');
 | 
			
		||||
 | 
			
		||||
                                                // Запись в реестр последнего калькулятора (подразумевается, что он новый и только что был записан)
 | 
			
		||||
                                                calculator.calculators.push(calculators[calculators.length - 1]);
 | 
			
		||||
 | 
			
		||||
                                                // Запись в журнал
 | 
			
		||||
                                                console.log('[КАЛЬКУЛЯТОР] Инициализирован калькулятор лазерной резки');
 | 
			
		||||
                                            }
 | 
			
		||||
                                        );
 | 
			
		||||
                                    } else {
 | 
			
		||||
                                        // Не найдено меню
 | 
			
		||||
 | 
			
		||||
@@ -205,53 +319,69 @@ let calculator = {
 | 
			
		||||
                                            // Найден элемент с результатами калькуляции
 | 
			
		||||
 | 
			
		||||
                                            // Инициализация разделителя перед меню
 | 
			
		||||
                                    calculator.generate.divider(result, 'beforebegin');
 | 
			
		||||
 | 
			
		||||
                                    setTimeout(() => {
 | 
			
		||||
                                            calculator.generate.divider(result, 'beforebegin').then(
 | 
			
		||||
                                                divider => {
 | 
			
		||||
                                                    // Запись калькулятора перед элементом с результатами калькуляции
 | 
			
		||||
                                        result.insertAdjacentHTML('beforebegin', request.responseText);
 | 
			
		||||
                                    }, 100);
 | 
			
		||||
                                                    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();
 | 
			
		||||
 | 
			
		||||
                                    setTimeout(() => {
 | 
			
		||||
                                            calculator.generate.divider().then(
 | 
			
		||||
                                                divider => {
 | 
			
		||||
                                                    // Запись калькулятора в конце калькулятора
 | 
			
		||||
                                        calculator.index.insertAdjacentHTML('beforeend', request.responseText);
 | 
			
		||||
                                    }, 100);
 | 
			
		||||
                                                    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,
 | 
			
		||||
            };
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										55
									
								
								mirzaev/calculator/system/public/js/cookie.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								mirzaev/calculator/system/public/js/cookie.js
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
        })
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
@@ -1,26 +1,39 @@
 | 
			
		||||
<link href="/css/auth.css" rel="stylesheet">
 | 
			
		||||
 | 
			
		||||
<section id="authentication">
 | 
			
		||||
    {% if account is not empty %}
 | 
			
		||||
    <h3>Аккаунт</h3>
 | 
			
		||||
    <div>
 | 
			
		||||
        <p><b>Почта:</b> <span>{{ account.email }}</span></p>
 | 
			
		||||
        <a class="exit" type="button" href='/account/deauthentication'>Выход</a>
 | 
			
		||||
    </div>
 | 
			
		||||
    {% else %}
 | 
			
		||||
    <h3>Аутентификация</h3>
 | 
			
		||||
    <form method="post" accept-charset="UTF-8">
 | 
			
		||||
        <input type="hidden" name="action" value="users/login">
 | 
			
		||||
 | 
			
		||||
        <input id="loginName" type="text" name="loginName" placeholder="Почта"
 | 
			
		||||
            value="{{ craft.app.user.rememberedUsername }}">
 | 
			
		||||
 | 
			
		||||
        <input id="password" type="password" name="password" placeholder="Пароль">
 | 
			
		||||
    <form method="POST" accept-charset="UTF-8">
 | 
			
		||||
        <input type="text" name="email" placeholder="Почта">
 | 
			
		||||
        <input type="password" name="password" placeholder="Пароль">
 | 
			
		||||
 | 
			
		||||
        <div class="submit">
 | 
			
		||||
            <label class="button unselectable" for="rememberMe">
 | 
			
		||||
                <i class="fas fa-unlock"></i>
 | 
			
		||||
            </label>
 | 
			
		||||
            <input type="checkbox" name="rememberMe" value="1">
 | 
			
		||||
 | 
			
		||||
            <input type="submit" value="Войти">
 | 
			
		||||
            <label class="button unselectable fas fa-unlock" for="remember"
 | 
			
		||||
                onclick="return remember_switch(this);"></label>
 | 
			
		||||
            <input type="checkbox" name="remember" value="1">
 | 
			
		||||
            <input type="submit" value="Войти" onclick="return authentication(this.parentElement.parentElement);">
 | 
			
		||||
        </div>
 | 
			
		||||
        <input type="submit" class="registration" value="Зарегистрироваться"
 | 
			
		||||
            onclick="return registration(this.parentElement);">
 | 
			
		||||
 | 
			
		||||
        {% if errorMessage is defined %}
 | 
			
		||||
        <p>{{ errorMessage }}</p>
 | 
			
		||||
        {% if errors is not empty %}
 | 
			
		||||
        {% if errors.account is not empty %}
 | 
			
		||||
        <ul class="errors">
 | 
			
		||||
            {% for error in errors.account %}
 | 
			
		||||
            <li>{{ error }}</li>
 | 
			
		||||
            {% endfor %}
 | 
			
		||||
        </ul>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </form>
 | 
			
		||||
    {% endif %}
 | 
			
		||||
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript" src="/js/auth.js" defer></script>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,6 +5,7 @@
 | 
			
		||||
	<div class="divider"></div>
 | 
			
		||||
</section>
 | 
			
		||||
 | 
			
		||||
<script type="text/javascript" src="/js/cookie.js" defer></script>
 | 
			
		||||
<script type="text/javascript" src="/js/calculator.js" defer></script>
 | 
			
		||||
<script>
 | 
			
		||||
	if (document.readyState === "complete") {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<section id="buyer">
 | 
			
		||||
<section id="buyer" class="unselectable">
 | 
			
		||||
    <input id="individual" type="radio" name="buyer" value="individual" {% if buyer is same as('individual') %}checked{% endif %}>
 | 
			
		||||
    <label type="button" for="individual">Физическое лицо</label>
 | 
			
		||||
    <input id="entity" type="radio" name="buyer" value="entity" {% if buyer is same as('entity') %}checked{% endif %}>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,9 +1,9 @@
 | 
			
		||||
<h3>Лазерная резка</h3>
 | 
			
		||||
<section id="laser">
 | 
			
		||||
<section class="calculator" data-calculator="laser">
 | 
			
		||||
    <div>
 | 
			
		||||
        <label>Металл</label>
 | 
			
		||||
        <div>
 | 
			
		||||
            <select name="metal">
 | 
			
		||||
            <select name="metal" data-calculator-parameter="metal">
 | 
			
		||||
                <option value="steel">Сталь</option>
 | 
			
		||||
                <option value="stainless_steel" selected>Нержавеющая сталь</option>
 | 
			
		||||
                <option value="brass">Латунь</option>
 | 
			
		||||
@@ -15,30 +15,30 @@
 | 
			
		||||
    <div>
 | 
			
		||||
        <label>Размер</label>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input type="text" class="measured" title="Длина" value="100">
 | 
			
		||||
            <input data-calculator-parameter="width" type="text" class="measured" title="Длина" value="100">
 | 
			
		||||
            <span class="unit unselectable">мм</span>
 | 
			
		||||
            <small>x</small>
 | 
			
		||||
            <input type="text" class="measured" title="Ширина" value="100">
 | 
			
		||||
            <input data-calculator-parameter="height" type="text" class="measured" title="Ширина" value="100">
 | 
			
		||||
            <span class="unit unselectable">мм</span>
 | 
			
		||||
            <small>x</small>
 | 
			
		||||
            <input type="text" class="measured" title="Толщина" value="1">
 | 
			
		||||
            <input data-calculator-parameter="length" type="text" class="measured" title="Толщина" value="1">
 | 
			
		||||
            <span class="unit unselectable">мм</span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <label>Отверстия</label>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input type="text" class="measured" title="Количество" value="1">
 | 
			
		||||
            <input data-calculator-parameter="holes" type="text" class="measured" title="Количество" value="1">
 | 
			
		||||
            <span class="unit unselectable">шт</span>
 | 
			
		||||
            <small>x</small>
 | 
			
		||||
            <input type="text" class="measured" title="Диаметр" value="1">
 | 
			
		||||
            <input data-calculator-parameter="diameter" type="text" class="measured" title="Диаметр" value="1">
 | 
			
		||||
            <span class="unit unselectable">мм</span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
    <div>
 | 
			
		||||
        <label>Количество</label>
 | 
			
		||||
        <div>
 | 
			
		||||
            <input type="text" class="measured" title="Количество" value="1">
 | 
			
		||||
            <input data-calculator-parameter="amount" type="text" class="measured" title="Количество" value="1">
 | 
			
		||||
            <span class="unit unselectable">шт</span>
 | 
			
		||||
        </div>
 | 
			
		||||
    </div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<section id="menu">
 | 
			
		||||
<section id="menu" class="unselectable">
 | 
			
		||||
    <a type="button" onclick="calculator.generate.calculators.laser(); return false;">
 | 
			
		||||
        <img src="/img/laser.png" title="Добавить лазерную резку">
 | 
			
		||||
        Лазерная резка
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,4 @@
 | 
			
		||||
<section id="complexity">
 | 
			
		||||
<section id="complexity" class="unselectable">
 | 
			
		||||
    <input id="easy" type="radio" name="complexity"  value="easy" {% if complexity is same as('easy') %}checked{% endif %}>
 | 
			
		||||
    <label type="button" for="easy">
 | 
			
		||||
        <img src="/img/easy.png" title="Лёгкий уровень сложности">
 | 
			
		||||
 
 | 
			
		||||
@@ -1 +1 @@
 | 
			
		||||
<div class="divider"></div>
 | 
			
		||||
<div class="divider unselectable"></div>
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,10 @@
 | 
			
		||||
<section id="result">
 | 
			
		||||
    <nav class="unselectable">
 | 
			
		||||
        <a id="calculate" type="button" onclick="return calculator.calculate();">Рассчитать</a>
 | 
			
		||||
    </nav>
 | 
			
		||||
    <div>
 | 
			
		||||
        <p>Расходы: <span id="expenses">0</span><span>рублей</span></p>
 | 
			
		||||
        <p>Доход: <span id="income">0</span><span>рублей</span></p>
 | 
			
		||||
        <p>Прибыль: <span id="profit">0</span><span>рублей</span></p>
 | 
			
		||||
        <p class="unselectable">Расходы: <span id="expenses">0</span><span>рублей</span></p>
 | 
			
		||||
        <p class="unselectable">Доход: <span id="income">0</span><span>рублей</span></p>
 | 
			
		||||
        <p class="unselectable">Прибыль: <span id="profit">0</span><span>рублей</span></p>
 | 
			
		||||
    </div>
 | 
			
		||||
</section>
 | 
			
		||||
 
 | 
			
		||||
@@ -5,5 +5,10 @@
 | 
			
		||||
        </a>
 | 
			
		||||
        <a class="link" href="/contacts" title="Связь с администрацией">Контакты</a>
 | 
			
		||||
        <a class="link" href="/journal" title="Записи рассчётов">Журнал</a>
 | 
			
		||||
        {% if account is not empty %}
 | 
			
		||||
        {% if account.permissions.settings is defined and account.permissions.settings == 1 %}
 | 
			
		||||
        <a class="link" href="/settings" title="Настройки калькуляторов">Настройки</a>
 | 
			
		||||
        {% endif %}
 | 
			
		||||
        {% endif %}
 | 
			
		||||
    </nav>
 | 
			
		||||
</header>
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user