Первая инициализация
This commit is contained in:
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
vendor/
|
||||||
|
cache/
|
41
codeception.yml
Normal file
41
codeception.yml
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# suite config
|
||||||
|
suites:
|
||||||
|
acceptance:
|
||||||
|
actor: AcceptanceTester
|
||||||
|
path: .
|
||||||
|
modules:
|
||||||
|
enabled:
|
||||||
|
- WebDriver:
|
||||||
|
url: http://git.hood.su/mirzaev/task_manager/view
|
||||||
|
host: 'Mirzaev:211041a7-665c-44e6-86e1-4719c7df4f2c@ondemand.saucelabs.com'
|
||||||
|
port: 80
|
||||||
|
browser: chrome
|
||||||
|
capabilities:
|
||||||
|
platform: 'Windows 10'
|
||||||
|
- \Helper\Acceptance
|
||||||
|
|
||||||
|
# add Codeception\Step\Retry trait to AcceptanceTester to enable retries
|
||||||
|
step_decorators:
|
||||||
|
- Codeception\Step\ConditionalAssertion
|
||||||
|
- Codeception\Step\TryTo
|
||||||
|
- Codeception\Step\Retry
|
||||||
|
|
||||||
|
extensions:
|
||||||
|
enabled: [Codeception\Extension\RunFailed]
|
||||||
|
|
||||||
|
params:
|
||||||
|
- env
|
||||||
|
|
||||||
|
gherkin: []
|
||||||
|
|
||||||
|
# additional paths
|
||||||
|
paths:
|
||||||
|
tests: mirzaev/beejee/tests
|
||||||
|
output: mirzaev/beejee/tests/_output
|
||||||
|
data: mirzaev/beejee/tests/_data
|
||||||
|
support: mirzaev/beejee/tests/_support
|
||||||
|
envs: mirzaev/beejee/tests/_envs
|
||||||
|
|
||||||
|
settings:
|
||||||
|
shuffle: false
|
||||||
|
lint: true
|
40
composer.json
Normal file
40
composer.json
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "mirzaev/beejee",
|
||||||
|
"description": "Test BeeJee",
|
||||||
|
"type": "project",
|
||||||
|
"license": "AGPL-3.0-or-later",
|
||||||
|
"homepage": "https://git.hood.su/mirzaev/beejee",
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Arsen Mirzaev Tatyano-Muradovich",
|
||||||
|
"email": "red@hood.su",
|
||||||
|
"role": "Developer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.4.0",
|
||||||
|
"ext-PDO": "^7.4",
|
||||||
|
"twbs/bootstrap": "^4.5",
|
||||||
|
"twig/twig": "^3.1"
|
||||||
|
},
|
||||||
|
"require-dev": {
|
||||||
|
"codeception/codeception": "^4.1",
|
||||||
|
"codeception/module-webdriver": "^1.0.0"
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\beejee\\": "mirzaev/beejee/system"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload-dev": {
|
||||||
|
"psr-4": {
|
||||||
|
"mirzaev\\beejee\\tests\\": "mirzaev/beejee/tests"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"post-update-cmd": [
|
||||||
|
"cp -R vendor/twbs/bootstrap/dist/css mirzaev/beejee/system/public/css/bootstrap",
|
||||||
|
"cp -R vendor/twbs/bootstrap/dist/js mirzaev/beejee/system/public/js/bootstrap"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
3868
composer.lock
generated
Normal file
3868
composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
74
mirzaev/beejee/system/controllers/authController.php
Normal file
74
mirzaev/beejee/system/controllers/authController.php
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\controllers;
|
||||||
|
|
||||||
|
use mirzaev\beejee\controllers\controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер аутентификации
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class authController extends controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Аутентификация
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function auth(array $params = null): ?array
|
||||||
|
{
|
||||||
|
$login = $params['login'] ?? $_COOKIE['login'];
|
||||||
|
$password = $params['password'] ?? $_COOKIE['password'];
|
||||||
|
|
||||||
|
if (isset($login, $password)) {
|
||||||
|
return $this->model->auth($login, $password);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Деаутентификация
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function deauth(array $params = null): void
|
||||||
|
{
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Выборочное удаление параметров сессии
|
||||||
|
unset($_SESSION['id']);
|
||||||
|
unset($_SESSION['admin']);
|
||||||
|
|
||||||
|
// session_unset();
|
||||||
|
// session_destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать представление HTML-сайдбара
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function genSidebarPanel(array $params = null): ?string
|
||||||
|
{
|
||||||
|
// Инициализация пользователя
|
||||||
|
$user = empty($this->auth()) ? false : true;
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
// Инициализация админ-прав
|
||||||
|
$admin = $_SESSION['admin'];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'auth' . DIRECTORY_SEPARATOR . 'auth_sidebar.html', ['user' => $user, 'admin' => $admin]);
|
||||||
|
}
|
||||||
|
}
|
148
mirzaev/beejee/system/controllers/controller.php
Normal file
148
mirzaev/beejee/system/controllers/controller.php
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\controllers;
|
||||||
|
|
||||||
|
use mirzaev\beejee\core,
|
||||||
|
mirzaev\beejee\models\model;
|
||||||
|
|
||||||
|
use Twig\Loader\FilesystemLoader,
|
||||||
|
Twig\Environment as view;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
class controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var model $model Модель
|
||||||
|
*/
|
||||||
|
protected model $model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var view $view Шаблонизатор представления
|
||||||
|
*/
|
||||||
|
protected view $view;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
// Установка значения по умолчанию для модели (если будет найдена)
|
||||||
|
$this->__get('model');
|
||||||
|
// Установка значения по умолчанию для шаблонизатора представлений
|
||||||
|
$this->__get('view');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отрисовка шаблона
|
||||||
|
*
|
||||||
|
* @param string $route Маршрут
|
||||||
|
*/
|
||||||
|
public function view(string $route)
|
||||||
|
{
|
||||||
|
// Чтение представления по шаблону пути: "/views/[controller]/index
|
||||||
|
// Никаких слоёв и шаблонизаторов
|
||||||
|
// Не стал в ядре записывать путь до шаблонов
|
||||||
|
if (file_exists($view = core::path() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views' . DIRECTORY_SEPARATOR . $route . DIRECTORY_SEPARATOR . 'index.html')) {
|
||||||
|
include $view;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записать свойство
|
||||||
|
*
|
||||||
|
* @param mixed $name Название
|
||||||
|
* @param mixed $value Значение
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __set($name, $value): void
|
||||||
|
{
|
||||||
|
if ($name === 'model') {
|
||||||
|
if (!isset($this->model)) {
|
||||||
|
$this->model = $value;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Exception('Запрещено переопределять модель');
|
||||||
|
}
|
||||||
|
} else if ($name === 'view') {
|
||||||
|
if (!isset($this->view)) {
|
||||||
|
$this->view = $value;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Exception('Запрещено переопределять шаблонизатор представления');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать свойство
|
||||||
|
*
|
||||||
|
* @param mixed $name Название
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get($name)
|
||||||
|
{
|
||||||
|
if ($name === 'model') {
|
||||||
|
if (isset($this->model)) {
|
||||||
|
// Если модель найдена
|
||||||
|
return $this->model;
|
||||||
|
} else {
|
||||||
|
// Инициализация класса модели
|
||||||
|
$model = preg_replace('/' . core::controllerPostfix() . '$/i', '', basename(get_class($this))) . core::modelPostfix();
|
||||||
|
// Иначе
|
||||||
|
if (class_exists($model_class = core::namespace() . '\\models\\' . $model)) {
|
||||||
|
// Если найдена одноимённая с контроллером модель (без постфикса)
|
||||||
|
return $this->model = new $model_class;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if ($name === 'view') {
|
||||||
|
if (isset($this->view)) {
|
||||||
|
// Если модель найдена
|
||||||
|
return $this->view;
|
||||||
|
} else {
|
||||||
|
$path = core::path() . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'views';
|
||||||
|
$loader = new FilesystemLoader($path);
|
||||||
|
|
||||||
|
return $this->view = (new view($loader, [
|
||||||
|
// 'cache' => $path . DIRECTORY_SEPARATOR . 'cache',
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить свойство на инициализированность
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __isset(string $name)
|
||||||
|
{
|
||||||
|
if ($name === 'model') {
|
||||||
|
return isset($this->model);
|
||||||
|
} else if ($name === 'view') {
|
||||||
|
return isset($this->view);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
}
|
26
mirzaev/beejee/system/controllers/errorsController.php
Normal file
26
mirzaev/beejee/system/controllers/errorsController.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\controllers;
|
||||||
|
|
||||||
|
use mirzaev\beejee\controllers\controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер основной страницы
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class errorsController extends controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Ответ 404
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function error404(): void
|
||||||
|
{
|
||||||
|
echo '404 Not Fount';
|
||||||
|
}
|
||||||
|
}
|
40
mirzaev/beejee/system/controllers/mainController.php
Normal file
40
mirzaev/beejee/system/controllers/mainController.php
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\controllers;
|
||||||
|
|
||||||
|
use mirzaev\beejee\controllers\controller,
|
||||||
|
mirzaev\beejee\controllers\tasksController;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер основной страницы
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class mainController extends controller
|
||||||
|
{
|
||||||
|
public function index(array $params)
|
||||||
|
{
|
||||||
|
$tasks = new tasksController;
|
||||||
|
|
||||||
|
// Нормализация
|
||||||
|
$page['current'] = filter_var($params['page'], FILTER_SANITIZE_NUMBER_INT);
|
||||||
|
$page['sort'] = filter_var($params['page'], FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
// Инициализация страниц заданий
|
||||||
|
$page['count'] = $tasks->count();
|
||||||
|
$page['current'] = filter_var($params['page'], FILTER_VALIDATE_INT, ['options' => ['default' => 1]]);
|
||||||
|
$page['previous'] = $page['current'] > 1 ? $page['current'] - 1 : 1;
|
||||||
|
$page['next'] = (int) $page['current'] < (int) $page['count'] ? $page['current'] + 1 : $page['current'];
|
||||||
|
|
||||||
|
// Инициализация сортировки
|
||||||
|
$sort = empty($params['sort']) ? 'id' : $params['sort'];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'main' . DIRECTORY_SEPARATOR . 'index.html', ['page' => $page, 'sort' => $sort]);
|
||||||
|
}
|
||||||
|
}
|
147
mirzaev/beejee/system/controllers/tasksController.php
Normal file
147
mirzaev/beejee/system/controllers/tasksController.php
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\controllers;
|
||||||
|
|
||||||
|
use mirzaev\beejee\controllers\controller;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Контроллер основной страницы
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\controllers
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class tasksController extends controller
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Записать в базу данных
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public function create(array $params): ?string
|
||||||
|
{
|
||||||
|
// Инициализация параметров
|
||||||
|
$name = $params['name'];
|
||||||
|
$email = $params['email'];
|
||||||
|
$task = $params['task'];
|
||||||
|
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
if (!isset($_SESSION['task_create_last']) || (isset($_SESSION['task_create_last']) && $_SESSION['task_create_last'] < time() - 5)) {
|
||||||
|
// Если это первый вызов или последний вызов был более 5 секунд назад
|
||||||
|
|
||||||
|
// Запись задания
|
||||||
|
$this->model->create($name, $email, $task);
|
||||||
|
|
||||||
|
$_SESSION['task_create_last'] = time();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'Следующее задание можно создать через ' . (5 - (time() - $_SESSION['task_create_last'])) . ' секунд';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать из базы данных
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function read(array $params): array
|
||||||
|
{
|
||||||
|
// Нормализация
|
||||||
|
$page['current'] = filter_var(filter_var($params['page'], FILTER_SANITIZE_NUMBER_INT), FILTER_VALIDATE_INT, ['options' => ['default' => 1]]);
|
||||||
|
$page['sort'] = filter_var($params['sort'], FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
// Инициализация параметров
|
||||||
|
$limit = 3;
|
||||||
|
$sort = empty($params['sort']) ? 'id' : $params['sort'];
|
||||||
|
$page = ($params['page'] - 1) * $limit;
|
||||||
|
|
||||||
|
// Чтение заданий
|
||||||
|
return $this->model->read($page, $limit, $sort);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить в базе данных
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function update(array $params): void
|
||||||
|
{
|
||||||
|
session_start();
|
||||||
|
|
||||||
|
// Нормализация
|
||||||
|
$page['id'] = filter_var(filter_var($params['id'], FILTER_SANITIZE_NUMBER_INT), FILTER_VALIDATE_INT, ['options' => ['default' => 1]]);
|
||||||
|
$page['name'] = filter_var($params['name'], FILTER_SANITIZE_STRING);
|
||||||
|
$page['email'] = filter_var($params['email'], FILTER_SANITIZE_EMAIL);
|
||||||
|
$page['task'] = filter_var($params['task'], FILTER_SANITIZE_STRING);
|
||||||
|
|
||||||
|
// Инициализация параметров
|
||||||
|
$id = (int) $params['id'];
|
||||||
|
$name = $params['name'];
|
||||||
|
$email = $params['email'];
|
||||||
|
$task = $params['task'];
|
||||||
|
$completed = $_SESSION['admin'] == 1 ? $params['completed'] : null;
|
||||||
|
|
||||||
|
// Запись задания
|
||||||
|
$this->model->update($id, $name, $email, $task, $completed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить из базы данных
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function delete(array $params): void
|
||||||
|
{
|
||||||
|
// Инициализация параметров
|
||||||
|
$id = (int) $params['id'];
|
||||||
|
|
||||||
|
// ВНИМАНИЕ: Сессию можно подобрать брутфорсом (либо ещё как-нибудь узнать)
|
||||||
|
// Я бы не стал так делать на нормальном проекте - писал привязку к IP, например
|
||||||
|
session_start();
|
||||||
|
if ($_SESSION['admin'] == 1) {
|
||||||
|
// Если пользователь является админстратором
|
||||||
|
$this->model->delete($id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Сгенерировать представление HTML-листа заданий
|
||||||
|
*
|
||||||
|
* @param array $params
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function genList(array $params): string
|
||||||
|
{
|
||||||
|
// Инициализация заданий
|
||||||
|
$tasks = $this->read($params);
|
||||||
|
|
||||||
|
// Инициализация админ-прав
|
||||||
|
session_start();
|
||||||
|
$admin = $_SESSION['admin'];
|
||||||
|
|
||||||
|
// Генерация представления
|
||||||
|
return $this->view->render(DIRECTORY_SEPARATOR . 'tasks' . DIRECTORY_SEPARATOR . 'list.html', ['tasks' => $tasks, 'admin' => $admin]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подсчитать количество заданий
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
return $this->model->count();
|
||||||
|
}
|
||||||
|
}
|
146
mirzaev/beejee/system/core.php
Normal file
146
mirzaev/beejee/system/core.php
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee;
|
||||||
|
|
||||||
|
use mirzaev\beejee\router;
|
||||||
|
|
||||||
|
use PDO,
|
||||||
|
PDOException;
|
||||||
|
|
||||||
|
use Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ядро
|
||||||
|
*
|
||||||
|
* Простая реализация ядра, для пары мелочей
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class core
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PDO $db Соединение с базой данных
|
||||||
|
*/
|
||||||
|
private static PDO $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var router $router Маршрутизатор
|
||||||
|
*/
|
||||||
|
private static router $router;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $path Корневая директория
|
||||||
|
*/
|
||||||
|
private static string $path;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $namespace Пространство имён
|
||||||
|
*/
|
||||||
|
private static string $namespace;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $postfix_controller Постфикс контроллеров
|
||||||
|
*/
|
||||||
|
private static string $postfix_controller = 'Controller';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var string $postfix_model Постфикс моделей
|
||||||
|
*/
|
||||||
|
private static string $postfix_model = 'Model';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
*
|
||||||
|
* @param string $db
|
||||||
|
* @param string $login
|
||||||
|
* @param string $password
|
||||||
|
* @param router|null $router
|
||||||
|
*
|
||||||
|
* @param router $router Маршрутизатор
|
||||||
|
*/
|
||||||
|
public function __construct(string $db = 'mysql:dbname=db;host=127.0.0.1', string $login = '', string $password = '', router $router = null)
|
||||||
|
{
|
||||||
|
// Инициализация маршрутизатора
|
||||||
|
self::$router = $router ?? new router;
|
||||||
|
|
||||||
|
// Инициализация корневого пространства имён
|
||||||
|
self::$namespace = __NAMESPACE__;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Инициализация PDO
|
||||||
|
self::$db = new PDO($db, $login, $password);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
throw new Exception('Проблемы при соединении с базой данных: ' . $e->getMessage(), $e->getCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обработка запроса
|
||||||
|
self::$router::handle();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Деструктор
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __destruct()
|
||||||
|
{
|
||||||
|
// Закрытие соединения
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать/записать корневую директорию
|
||||||
|
*
|
||||||
|
* @var string|null $path Путь
|
||||||
|
*
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function path(string $path = null): string
|
||||||
|
{
|
||||||
|
return self::$path = (string) ($path ?? self::$path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать/записать соединение с базой данных
|
||||||
|
*
|
||||||
|
* @var PDO|null $db Соединение с базой данных
|
||||||
|
*
|
||||||
|
* @return PDO
|
||||||
|
*/
|
||||||
|
public static function db(PDO $db = null): PDO
|
||||||
|
{
|
||||||
|
return self::$db = $db ?? self::$db;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать постфикс контроллеров
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function controllerPostfix(): ?string
|
||||||
|
{
|
||||||
|
return self::$postfix_controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать постфикс моделей
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function modelPostfix(): ?string
|
||||||
|
{
|
||||||
|
return self::$postfix_model;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать пространство имён
|
||||||
|
*
|
||||||
|
* @return string|null
|
||||||
|
*/
|
||||||
|
public static function namespace(): ?string
|
||||||
|
{
|
||||||
|
return self::$namespace;
|
||||||
|
}
|
||||||
|
}
|
73
mirzaev/beejee/system/models/authModel.php
Normal file
73
mirzaev/beejee/system/models/authModel.php
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\models;
|
||||||
|
|
||||||
|
use mirzaev\beejee\models\model;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель регистрации, аутентификации и авторизации
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class authModel extends model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Аутентификация
|
||||||
|
*
|
||||||
|
* @param string $login Входной
|
||||||
|
* @param string $password Пароль
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
public function auth(string $login, string $password): ?array
|
||||||
|
{
|
||||||
|
$user = $this->search($login);
|
||||||
|
|
||||||
|
if (empty($user)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password_verify($password, $user['password'])) {
|
||||||
|
// Если пароли совпадают
|
||||||
|
|
||||||
|
// Инициализация сессии
|
||||||
|
session_start();
|
||||||
|
$_SESSION['id'] = $user['id'];
|
||||||
|
$_SESSION['admin'] = $user['admin'];
|
||||||
|
|
||||||
|
// Инициализация cookies
|
||||||
|
setcookie("login", $login, ['expires' => time() + 50000, 'SameSite' => 'Strict']);
|
||||||
|
setcookie("password", $password, ['expires' => time() + 50000, 'SameSite' => 'Strict']);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Поиск пользователя по входному псевдониму
|
||||||
|
*
|
||||||
|
* @param string $login Входной
|
||||||
|
*
|
||||||
|
* @return array|null
|
||||||
|
*/
|
||||||
|
private function search(string $login): ?array
|
||||||
|
{
|
||||||
|
// Инициализация
|
||||||
|
$request = $this->db->prepare("SELECT * FROM `users` WHERE `login` = :login LIMIT 1");
|
||||||
|
|
||||||
|
// Параметры
|
||||||
|
$params = [
|
||||||
|
':login' => $login,
|
||||||
|
];
|
||||||
|
|
||||||
|
// Отправка
|
||||||
|
$request->execute($params);
|
||||||
|
|
||||||
|
return (array) $request->fetch(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
}
|
89
mirzaev/beejee/system/models/model.php
Normal file
89
mirzaev/beejee/system/models/model.php
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\models;
|
||||||
|
|
||||||
|
use mirzaev\beejee\core;
|
||||||
|
|
||||||
|
use Exception,
|
||||||
|
PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
class model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var PDO $db Соединение с базой данных
|
||||||
|
*/
|
||||||
|
protected PDO $db;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Конструктор
|
||||||
|
*
|
||||||
|
* @param PDO|null $db Соединение с базой данных
|
||||||
|
*/
|
||||||
|
public function __construct(PDO $db = null)
|
||||||
|
{
|
||||||
|
$this->db = $db ?? core::db();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Записать свойство
|
||||||
|
*
|
||||||
|
* @param mixed $name Название
|
||||||
|
* @param mixed $value Значение
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function __set($name, $value): void
|
||||||
|
{
|
||||||
|
if ($name === 'db') {
|
||||||
|
if (!isset($this->db)) {
|
||||||
|
$this->db = $value;
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
throw new Exception('Запрещено переопределять соединение с базой данных');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать свойство
|
||||||
|
*
|
||||||
|
* @param mixed $name Название
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __get($name)
|
||||||
|
{
|
||||||
|
if ($name === 'db') {
|
||||||
|
return $this->db;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверить свойство на инициализированность
|
||||||
|
*
|
||||||
|
* @param string $name Название
|
||||||
|
*
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
public function __isset(string $name)
|
||||||
|
{
|
||||||
|
if ($name === 'db') {
|
||||||
|
return isset($this->db);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Exception('Свойство не найдено: ' . $name);
|
||||||
|
}
|
||||||
|
}
|
164
mirzaev/beejee/system/models/tasksModel.php
Normal file
164
mirzaev/beejee/system/models/tasksModel.php
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee\models;
|
||||||
|
|
||||||
|
use mirzaev\beejee\models\model;
|
||||||
|
|
||||||
|
use PDO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Модель заданий
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee\models
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class tasksModel extends model
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Создать запись
|
||||||
|
*
|
||||||
|
* @param string $name Имя
|
||||||
|
* @param string $email Почта
|
||||||
|
* @param string $task Задание
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function create(string $name, string $email, string $task): array
|
||||||
|
{
|
||||||
|
// Инициализация
|
||||||
|
$request = $this->db->prepare('INSERT INTO `tasks` (`name`, `email`, `task`) VALUES (:name, :email, :task)');
|
||||||
|
|
||||||
|
// Параметры
|
||||||
|
$request->bindValue(':name', $name, PDO::PARAM_STR);
|
||||||
|
$request->bindValue(':email', $email, PDO::PARAM_STR);
|
||||||
|
$request->bindValue(':task', $task, PDO::PARAM_STR);
|
||||||
|
|
||||||
|
// Отправка
|
||||||
|
$request->execute();
|
||||||
|
|
||||||
|
return (array) $request->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Прочитать запись
|
||||||
|
*
|
||||||
|
* @param int $page Страница
|
||||||
|
* @param int $limit Количество заданий
|
||||||
|
* @param string $sort Сортировка
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function read(int $page, int $limit, string $sort): array
|
||||||
|
{
|
||||||
|
// Нормализация сортировки по белому списку
|
||||||
|
if ($sort === 'id' || $sort === 'name' || $sort === 'email' || $sort === 'task') {
|
||||||
|
// Инициализация
|
||||||
|
$request = $this->db->prepare('SELECT * FROM `tasks` ORDER BY `' . $sort . '` LIMIT :page, :limit');
|
||||||
|
|
||||||
|
// Параметры
|
||||||
|
$request->bindValue(':page', $page, PDO::PARAM_INT);
|
||||||
|
$request->bindValue(':limit', $limit, PDO::PARAM_INT);
|
||||||
|
|
||||||
|
// Отправка
|
||||||
|
$request->execute();
|
||||||
|
|
||||||
|
return (array) $request->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновить запись
|
||||||
|
*
|
||||||
|
* @param int $name Идентификатор
|
||||||
|
* @param string $name Имя
|
||||||
|
* @param string $email Почта
|
||||||
|
* @param string $task Задание
|
||||||
|
* @param string $task Статус о завершении
|
||||||
|
*
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
public function update(int $id, string $name = null, string $email = null, string $task = null, string $completed = null): array
|
||||||
|
{
|
||||||
|
// Инициализация строки запроса
|
||||||
|
$request = 'UPDATE `tasks` SET';
|
||||||
|
|
||||||
|
// Проверка на то, что уже стоит какое-то значение
|
||||||
|
// Нужно для того, чтобы ставить запятые
|
||||||
|
$comma = false;
|
||||||
|
|
||||||
|
if (!is_null($name)) {
|
||||||
|
// Имя
|
||||||
|
$request .= '`name` = :name';
|
||||||
|
$params[':name'] = $name;
|
||||||
|
$comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($email)) {
|
||||||
|
// Почта
|
||||||
|
$request .= ($comma ? ', ' : '') . '`email` = :email';
|
||||||
|
$params[':email'] = $email;
|
||||||
|
$comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($task)) {
|
||||||
|
// Задание
|
||||||
|
$request .= ($comma ? ', ' : '') . '`task` = :task';
|
||||||
|
$params[':task'] = $task;
|
||||||
|
$comma = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_null($completed) && $completed == 1) {
|
||||||
|
// Статус завершения
|
||||||
|
$request .= ($comma ? ', ' : '') . '`completed` = NOT `completed`';
|
||||||
|
}
|
||||||
|
|
||||||
|
$request .= ' WHERE `id` = :id';
|
||||||
|
$params[':id'] = $id;
|
||||||
|
|
||||||
|
echo $request;
|
||||||
|
// Инициализация запроса
|
||||||
|
$request = $this->db->prepare($request);
|
||||||
|
|
||||||
|
// Отправка
|
||||||
|
$request->execute($params);
|
||||||
|
|
||||||
|
return (array) $request->fetchAll(PDO::FETCH_ASSOC);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удалить запись
|
||||||
|
*
|
||||||
|
* @param int $name Идентификатор
|
||||||
|
*
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public function delete(int $id): bool
|
||||||
|
{
|
||||||
|
// Инициализация запроса
|
||||||
|
$request = 'DELETE FROM `tasks` WHERE `id` = :id';
|
||||||
|
|
||||||
|
// Параметры
|
||||||
|
$params = [
|
||||||
|
':id' => $id
|
||||||
|
];
|
||||||
|
|
||||||
|
// Запрос
|
||||||
|
return (bool) $this->db->prepare($request)->execute($params);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подсчитать количество заданий
|
||||||
|
*
|
||||||
|
* @return int
|
||||||
|
*/
|
||||||
|
public function count(): int
|
||||||
|
{
|
||||||
|
// Запрос к базе данных
|
||||||
|
$request = ceil($this->db->query('SELECT count(*) as count FROM `tasks`')->fetch()['count'] / 3);
|
||||||
|
|
||||||
|
return (int) (empty($request) ? 1 : $request);
|
||||||
|
}
|
||||||
|
}
|
109
mirzaev/beejee/system/public/Nginx_1.17_vhost.conf
Normal file
109
mirzaev/beejee/system/public/Nginx_1.17_vhost.conf
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
# ----------------------------
|
||||||
|
# Host config
|
||||||
|
# ----------------------------
|
||||||
|
|
||||||
|
server {
|
||||||
|
|
||||||
|
listen %ip%:%httpport% default;
|
||||||
|
listen %ip%:%httpsport% ssl http2 default;
|
||||||
|
|
||||||
|
server_name catalog.loc %aliases%;
|
||||||
|
root '%hostdir%';
|
||||||
|
limit_conn addr 64;
|
||||||
|
autoindex off;
|
||||||
|
index index.php index.html index.htm;
|
||||||
|
|
||||||
|
ssl_certificate '%sprogdir%/userdata/config/cert_files/server.crt';
|
||||||
|
ssl_certificate_key '%sprogdir%/userdata/config/cert_files/server.key';
|
||||||
|
# ssl_trusted_certificate '';
|
||||||
|
|
||||||
|
# Force HTTPS
|
||||||
|
# add_header Strict-Transport-Security 'max-age=2592000' always;
|
||||||
|
# if ($scheme ~* ^(?!https).*$) {
|
||||||
|
# return 301 https://$host$request_uri;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Force www.site.com => site.com
|
||||||
|
# if ($host ~* ^www\.(.+)$) {
|
||||||
|
# return 301 $scheme://$1$request_uri;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Disable access to backup/config/command/log files
|
||||||
|
# if ($uri ~* ^.+\.(?:bak|co?nf|in[ci]|log|orig|sh|sql|tar|sql|t?gz|cmd|bat)$) {
|
||||||
|
# return 404;
|
||||||
|
# }
|
||||||
|
|
||||||
|
# Disable access to hidden files/folders
|
||||||
|
if ($uri ~* /\.(?!well-known)) {
|
||||||
|
return 404;
|
||||||
|
}
|
||||||
|
|
||||||
|
# Disable MIME sniffing
|
||||||
|
add_header X-Content-Type-Options 'nosniff' always;
|
||||||
|
|
||||||
|
location ~* ^.+\.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?|ttf|ttc|otf|eot|woff2?)$ {
|
||||||
|
expires 1d;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location / {
|
||||||
|
# Force index.php routing (if not found)
|
||||||
|
try_files $uri $uri/ /index.php$is_args$args;
|
||||||
|
|
||||||
|
# Force index.php routing (all requests)
|
||||||
|
# rewrite ^/(.*)$ /index.php?/$1 last;
|
||||||
|
|
||||||
|
location ~ \.php$ {
|
||||||
|
try_files $fastcgi_script_name =404;
|
||||||
|
|
||||||
|
# limit_conn addr 16;
|
||||||
|
# limit_req zone=flood burst=32 nodelay;
|
||||||
|
|
||||||
|
# add_header X-Frame-Options 'SAMEORIGIN' always;
|
||||||
|
# add_header Referrer-Policy 'no-referrer-when-downgrade' always;
|
||||||
|
|
||||||
|
# CSP syntax: <host-source> <scheme-source>(http: https: data: mediastream: blob: filesystem:) 'self' 'unsafe-inline' 'unsafe-eval' 'none'
|
||||||
|
# Content-Security-Policy-Report-Only (report-uri https://site.com/csp/)
|
||||||
|
# add_header Content-Security-Policy "default-src 'self'; connect-src 'self'; font-src 'self'; frame-src 'self'; img-src 'self'; manifest-src 'self'; media-src 'self'; object-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests" always;
|
||||||
|
|
||||||
|
fastcgi_pass backend;
|
||||||
|
include '%sprogdir%/userdata/config/nginx_fastcgi_params.txt';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Service configuration (do not edit!)
|
||||||
|
# ----------------------------
|
||||||
|
location /openserver/ {
|
||||||
|
root '%sprogdir%/modules/system/html';
|
||||||
|
autoindex off;
|
||||||
|
index index.php index.html index.htm;
|
||||||
|
|
||||||
|
%allow%allow all;
|
||||||
|
allow 127.0.0.0/8;
|
||||||
|
allow ::1/128;
|
||||||
|
allow %ips%;
|
||||||
|
deny all;
|
||||||
|
|
||||||
|
location ~* ^/openserver/.+\.(?:css(\.map)?|js(\.map)?|jpe?g|png|gif|ico|cur|heic|webp|tiff?|mp3|m4a|aac|ogg|midi?|wav|mp4|mov|webm|mpe?g|avi|ogv|flv|wmv|svgz?|ttf|ttc|otf|eot|woff2?)$ {
|
||||||
|
expires 1d;
|
||||||
|
access_log off;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /openserver/server-status {
|
||||||
|
stub_status on;
|
||||||
|
}
|
||||||
|
|
||||||
|
location ~ ^/openserver/.*\.php$ {
|
||||||
|
try_files $fastcgi_script_name =404;
|
||||||
|
fastcgi_index index.php;
|
||||||
|
fastcgi_pass backend;
|
||||||
|
include '%sprogdir%/userdata/config/nginx_fastcgi_params.txt';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
# End service configuration
|
||||||
|
# ----------------------------
|
||||||
|
}
|
||||||
|
|
||||||
|
# ----------------------------
|
||||||
|
# End host config
|
||||||
|
# ----------------------------
|
3872
mirzaev/beejee/system/public/css/bootstrap/bootstrap-grid.css
vendored
Normal file
3872
mirzaev/beejee/system/public/css/bootstrap/bootstrap-grid.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
mirzaev/beejee/system/public/css/bootstrap/bootstrap-grid.min.css
vendored
Normal file
7
mirzaev/beejee/system/public/css/bootstrap/bootstrap-grid.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
326
mirzaev/beejee/system/public/css/bootstrap/bootstrap-reboot.css
vendored
Normal file
326
mirzaev/beejee/system/public/css/bootstrap/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,326 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2020 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2020 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/
|
||||||
|
*,
|
||||||
|
*::before,
|
||||||
|
*::after {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: sans-serif;
|
||||||
|
line-height: 1.15;
|
||||||
|
-webkit-text-size-adjust: 100%;
|
||||||
|
-webkit-tap-highlight-color: rgba(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
article, aside, figcaption, figure, footer, header, hgroup, main, nav, section {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 1.5;
|
||||||
|
color: #212529;
|
||||||
|
text-align: left;
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
[tabindex="-1"]:focus:not(:focus-visible) {
|
||||||
|
outline: 0 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box;
|
||||||
|
height: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1, h2, h3, h4, h5, h6 {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
abbr[title],
|
||||||
|
abbr[data-original-title] {
|
||||||
|
text-decoration: underline;
|
||||||
|
-webkit-text-decoration: underline dotted;
|
||||||
|
text-decoration: underline dotted;
|
||||||
|
cursor: help;
|
||||||
|
border-bottom: 0;
|
||||||
|
-webkit-text-decoration-skip-ink: none;
|
||||||
|
text-decoration-skip-ink: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
address {
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
font-style: normal;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul,
|
||||||
|
dl {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol ol,
|
||||||
|
ul ul,
|
||||||
|
ol ul,
|
||||||
|
ul ol {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
dt {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
position: relative;
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: #007bff;
|
||||||
|
text-decoration: none;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:hover {
|
||||||
|
color: #0056b3;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]) {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not([href]):not([class]):hover {
|
||||||
|
color: inherit;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre,
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
overflow: auto;
|
||||||
|
-ms-overflow-style: scrollbar;
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0 0 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
vertical-align: middle;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
overflow: hidden;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
caption {
|
||||||
|
padding-top: 0.75rem;
|
||||||
|
padding-bottom: 0.75rem;
|
||||||
|
color: #6c757d;
|
||||||
|
text-align: left;
|
||||||
|
caption-side: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
text-align: inherit;
|
||||||
|
text-align: -webkit-match-parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: inline-block;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:focus {
|
||||||
|
outline: 1px dotted;
|
||||||
|
outline: 5px auto -webkit-focus-ring-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
input,
|
||||||
|
button,
|
||||||
|
select,
|
||||||
|
optgroup,
|
||||||
|
textarea {
|
||||||
|
margin: 0;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
line-height: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
input {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
select {
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[role="button"] {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
select {
|
||||||
|
word-wrap: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
button:not(:disabled),
|
||||||
|
[type="button"]:not(:disabled),
|
||||||
|
[type="reset"]:not(:disabled),
|
||||||
|
[type="submit"]:not(:disabled) {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
padding: 0;
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
input[type="radio"],
|
||||||
|
input[type="checkbox"] {
|
||||||
|
box-sizing: border-box;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
resize: vertical;
|
||||||
|
}
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
legend {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
padding: 0;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
line-height: inherit;
|
||||||
|
color: inherit;
|
||||||
|
white-space: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
outline-offset: -2px;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
font: inherit;
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
output {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
File diff suppressed because one or more lines are too long
8
mirzaev/beejee/system/public/css/bootstrap/bootstrap-reboot.min.css
vendored
Normal file
8
mirzaev/beejee/system/public/css/bootstrap/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
/*!
|
||||||
|
* Bootstrap Reboot v4.5.3 (https://getbootstrap.com/)
|
||||||
|
* Copyright 2011-2020 The Bootstrap Authors
|
||||||
|
* Copyright 2011-2020 Twitter, Inc.
|
||||||
|
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/main/LICENSE)
|
||||||
|
* Forked from Normalize.css, licensed MIT (https://github.com/necolas/normalize.css/blob/master/LICENSE.md)
|
||||||
|
*/*,::after,::before{box-sizing:border-box}html{font-family:sans-serif;line-height:1.15;-webkit-text-size-adjust:100%;-webkit-tap-highlight-color:transparent}article,aside,figcaption,figure,footer,header,hgroup,main,nav,section{display:block}body{margin:0;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"Noto Sans",sans-serif,"Apple Color Emoji","Segoe UI Emoji","Segoe UI Symbol","Noto Color Emoji";font-size:1rem;font-weight:400;line-height:1.5;color:#212529;text-align:left;background-color:#fff}[tabindex="-1"]:focus:not(:focus-visible){outline:0!important}hr{box-sizing:content-box;height:0;overflow:visible}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{text-decoration:underline;-webkit-text-decoration:underline dotted;text-decoration:underline dotted;cursor:help;border-bottom:0;-webkit-text-decoration-skip-ink:none;text-decoration-skip-ink:none}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}b,strong{font-weight:bolder}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}a{color:#007bff;text-decoration:none;background-color:transparent}a:hover{color:#0056b3;text-decoration:underline}a:not([href]):not([class]){color:inherit;text-decoration:none}a:not([href]):not([class]):hover{color:inherit;text-decoration:none}code,kbd,pre,samp{font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;font-size:1em}pre{margin-top:0;margin-bottom:1rem;overflow:auto;-ms-overflow-style:scrollbar}figure{margin:0 0 1rem}img{vertical-align:middle;border-style:none}svg{overflow:hidden;vertical-align:middle}table{border-collapse:collapse}caption{padding-top:.75rem;padding-bottom:.75rem;color:#6c757d;text-align:left;caption-side:bottom}th{text-align:inherit;text-align:-webkit-match-parent}label{display:inline-block;margin-bottom:.5rem}button{border-radius:0}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,optgroup,select,textarea{margin:0;font-family:inherit;font-size:inherit;line-height:inherit}button,input{overflow:visible}button,select{text-transform:none}[role=button]{cursor:pointer}select{word-wrap:normal}[type=button],[type=reset],[type=submit],button{-webkit-appearance:button}[type=button]:not(:disabled),[type=reset]:not(:disabled),[type=submit]:not(:disabled),button:not(:disabled){cursor:pointer}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{padding:0;border-style:none}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}textarea{overflow:auto;resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;max-width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit;color:inherit;white-space:normal}progress{vertical-align:baseline}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{outline-offset:-2px;-webkit-appearance:none}[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{font:inherit;-webkit-appearance:button}output{display:inline-block}summary{display:list-item;cursor:pointer}template{display:none}[hidden]{display:none!important}
|
||||||
|
/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
File diff suppressed because one or more lines are too long
10263
mirzaev/beejee/system/public/css/bootstrap/bootstrap.css
vendored
Normal file
10263
mirzaev/beejee/system/public/css/bootstrap/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
mirzaev/beejee/system/public/css/bootstrap/bootstrap.min.css
vendored
Normal file
7
mirzaev/beejee/system/public/css/bootstrap/bootstrap.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
18
mirzaev/beejee/system/public/css/main.css
Normal file
18
mirzaev/beejee/system/public/css/main.css
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
.task_delete_buttons {
|
||||||
|
color: rgb(202, 92, 92);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task_delete_buttons:hover {
|
||||||
|
color: rgb(165, 67, 67);
|
||||||
|
}
|
||||||
|
|
||||||
|
.task_buttons:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task_button_update:focus, .task_button_update {
|
||||||
|
box-shadow: none;
|
||||||
|
outline: none;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
}
|
BIN
mirzaev/beejee/system/public/img/avatar.webp
Normal file
BIN
mirzaev/beejee/system/public/img/avatar.webp
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
28
mirzaev/beejee/system/public/index.php
Normal file
28
mirzaev/beejee/system/public/index.php
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee;
|
||||||
|
|
||||||
|
use mirzaev\beejee\core,
|
||||||
|
mirzaev\beejee\router;
|
||||||
|
|
||||||
|
// Автозагрузка
|
||||||
|
require __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'vendor' . DIRECTORY_SEPARATOR . 'autoload.php';
|
||||||
|
|
||||||
|
// Инициализация корневой директории
|
||||||
|
core::path(__DIR__);
|
||||||
|
|
||||||
|
// Запись маршрутов
|
||||||
|
router::create('/', 'main', 'index');
|
||||||
|
router::create('task/create', 'tasks', 'create', 'POST');
|
||||||
|
router::create('task/update', 'tasks', 'update', 'POST');
|
||||||
|
router::create('task/delete', 'tasks', 'delete', 'POST');
|
||||||
|
router::create('tasks', 'tasks', 'genList');
|
||||||
|
router::create('tasks/count', 'tasks', 'count');
|
||||||
|
router::create('auth', 'auth', 'auth', 'POST');
|
||||||
|
router::create('deauth', 'auth', 'deauth', 'POST');
|
||||||
|
router::create('auth', 'auth', 'genSidebarPanel', 'GET');
|
||||||
|
router::create('reg', 'auth', 'reg', 'POST');
|
||||||
|
|
||||||
|
new Core('mysql:dbname=beejee;host=127.0.0.1', 'root', 'root');
|
56
mirzaev/beejee/system/public/js/auth.js
Normal file
56
mirzaev/beejee/system/public/js/auth.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
function auth(button) {
|
||||||
|
button.removeAttribute('onclick');
|
||||||
|
|
||||||
|
let login = document.getElementById('auth_sidebar_login').value;
|
||||||
|
let password = document.getElementById('auth_sidebar_password').value;
|
||||||
|
|
||||||
|
let body = 'login=' + encodeURIComponent(login) + '&password=' + encodeURIComponent(password);
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/auth');
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
task_read();
|
||||||
|
auth_html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function auth_html(auth) {
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('GET', '/auth');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
document.getElementById('sidebar').innerHTML = this.responseText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deauth(button) {
|
||||||
|
button.removeAttribute('onclick');
|
||||||
|
|
||||||
|
delete_cookie('login');
|
||||||
|
delete_cookie('password');
|
||||||
|
delete_cookie('admin');
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/deauth');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
task_read();
|
||||||
|
auth_html();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function delete_cookie(name) {
|
||||||
|
let date = new Date();
|
||||||
|
date.setTime(date.getTime() - 1);
|
||||||
|
document.cookie = name += "=; expires=" + date.toGMTString();
|
||||||
|
}
|
||||||
|
|
||||||
|
auth_html();
|
7031
mirzaev/beejee/system/public/js/bootstrap/bootstrap.bundle.js
vendored
Normal file
7031
mirzaev/beejee/system/public/js/bootstrap/bootstrap.bundle.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
mirzaev/beejee/system/public/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
7
mirzaev/beejee/system/public/js/bootstrap/bootstrap.bundle.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
4418
mirzaev/beejee/system/public/js/bootstrap/bootstrap.js
vendored
Normal file
4418
mirzaev/beejee/system/public/js/bootstrap/bootstrap.js
vendored
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
7
mirzaev/beejee/system/public/js/bootstrap/bootstrap.min.js
vendored
Normal file
7
mirzaev/beejee/system/public/js/bootstrap/bootstrap.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1 @@
|
|||||||
|
|
200
mirzaev/beejee/system/public/js/tasks.js
Normal file
200
mirzaev/beejee/system/public/js/tasks.js
Normal file
@@ -0,0 +1,200 @@
|
|||||||
|
function task_create(form) {
|
||||||
|
let name = document.getElementById('task_create_panel_name').value;
|
||||||
|
let email = document.getElementById('task_create_panel_email').value;
|
||||||
|
let task = document.getElementById('task_create_panel_task').value;
|
||||||
|
|
||||||
|
let body = 'name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email) + '&task=' + encodeURIComponent(task);
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/task/create');
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
if (this.responseText !== '') {
|
||||||
|
document.getElementById('task_create_warning').innerHTML = this.responseText;
|
||||||
|
if (document.getElementById('task_create_warning').hasAttribute('style')) {
|
||||||
|
document.getElementById('task_create_warning').removeAttribute('style');
|
||||||
|
setTimeout(function () {
|
||||||
|
document.getElementById('task_create_warning').setAttribute('style', 'display: none;');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (document.getElementById('task_create_success').hasAttribute('style')) {
|
||||||
|
document.getElementById('task_create_success').removeAttribute('style');
|
||||||
|
setTimeout(function () {
|
||||||
|
document.getElementById('task_create_success').setAttribute('style', 'display: none;');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
task_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_read(page) {
|
||||||
|
document.getElementById('button_forward').removeAttribute('onclick');
|
||||||
|
document.getElementById('button_back').removeAttribute('onclick');
|
||||||
|
|
||||||
|
if (typeof page === 'undefined') {
|
||||||
|
page = document.getElementById('page_сurrent').innerHTML;
|
||||||
|
if (typeof page === 'undefined') {
|
||||||
|
page = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('GET', '/tasks/count');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
let pages_count;
|
||||||
|
document.getElementById('pages_сount').innerHTML = pages_count = this.responseText;
|
||||||
|
if (typeof pages_count === 'undefined') {
|
||||||
|
document.getElementById('pages_сount').innerHTML = pages_count = page;
|
||||||
|
}
|
||||||
|
task_page_buttons(page, pages_count);
|
||||||
|
task_read_request(page);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_read_request(page) {
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('page', page);
|
||||||
|
history.pushState(null, null, url);
|
||||||
|
document.getElementById('page_сurrent').innerHTML = page;
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
|
||||||
|
http.open('GET', '/tasks' + url.search)
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
document.getElementById('tasks').innerHTML = this.responseText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_update(element) {
|
||||||
|
let id = element.getAttribute('id');
|
||||||
|
let name = element.children[1].innerHTML;
|
||||||
|
let email = element.children[2].innerHTML;
|
||||||
|
let task = element.children[3].innerHTML;
|
||||||
|
|
||||||
|
document.getElementById('form_task_create').setAttribute('onsubmit', 'task_update_send(document.getElementById(' + id + ')); return false;')
|
||||||
|
|
||||||
|
let update_button_task = document.getElementById('update_button_task_' + id);
|
||||||
|
update_button_task.parentNode.removeChild(update_button_task);
|
||||||
|
|
||||||
|
element.children[1].innerHTML = '<input type="text" class="form-control-sm">';
|
||||||
|
element.children[2].innerHTML = '<input type="email" type="text" class="form-control-sm">';
|
||||||
|
element.children[3].innerHTML = '<input type="text" class="form-control-sm">';
|
||||||
|
|
||||||
|
element.children[1].children[0].value = name;
|
||||||
|
element.children[2].children[0].value = email;
|
||||||
|
element.children[3].children[0].value = task;
|
||||||
|
|
||||||
|
element.children[4].innerHTML = '<button type="submit" class="p-0 task_button_update"><i class="fas fa-check mr-2 text-success"></i></button>' + element.children[4].innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_update_send(element) {
|
||||||
|
let id = element.getAttribute('id');
|
||||||
|
let name = element.children[1].children[0].value;
|
||||||
|
let email = element.children[2].children[0].value;
|
||||||
|
let task = element.children[3].children[0].value;
|
||||||
|
|
||||||
|
let body = 'id=' + encodeURIComponent(id) + '&name=' + encodeURIComponent(name) + '&email=' + encodeURIComponent(email) + '&task=' + encodeURIComponent(task);
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/task/update');
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
task_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_change_status(element) {
|
||||||
|
let id = element.getAttribute('id');
|
||||||
|
|
||||||
|
let body = 'id=' + encodeURIComponent(id) + '&completed=1';
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/task/update');
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
task_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_delete(element) {
|
||||||
|
let id = element.getAttribute('id');
|
||||||
|
|
||||||
|
let body = 'id=' + encodeURIComponent(id);
|
||||||
|
|
||||||
|
let http = new XMLHttpRequest();
|
||||||
|
http.open('POST', '/task/delete');
|
||||||
|
http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
||||||
|
http.onreadystatechange = function () {
|
||||||
|
if (http.readyState === XMLHttpRequest.DONE && http.status === 200) {
|
||||||
|
task_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
http.send(body);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function task_sort(element) {
|
||||||
|
if (element.selectedIndex) {
|
||||||
|
const url = new URL(window.location);
|
||||||
|
url.searchParams.set('sort', element.value);
|
||||||
|
history.pushState(null, null, url);
|
||||||
|
|
||||||
|
task_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function task_page_buttons(current, count) {
|
||||||
|
let previous;
|
||||||
|
if (current > 1) {
|
||||||
|
previous = parseInt(current) - 1;
|
||||||
|
} else {
|
||||||
|
previous = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
let next;
|
||||||
|
if (current < count) {
|
||||||
|
next = parseInt(current) + 1;
|
||||||
|
} else {
|
||||||
|
next = current;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current > count) {
|
||||||
|
current = parseInt(count) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current < 1) {
|
||||||
|
current = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('button_forward').setAttribute('onclick', 'task_read(' + next + ')');
|
||||||
|
document.getElementById('button_back').setAttribute('onclick', 'task_read(' + previous + ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.forms.tasks_create_panel.addEventListener('submit', function (event) {
|
||||||
|
if (document.forms.tasks_create_panel.checkValidity() === false) {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
} else {
|
||||||
|
task_create(document.forms.tasks_create_panel);
|
||||||
|
}
|
||||||
|
document.forms.tasks_create_panel.classList.add('was-validated');
|
||||||
|
}, false);
|
||||||
|
|
||||||
|
task_read();
|
117
mirzaev/beejee/system/router.php
Normal file
117
mirzaev/beejee/system/router.php
Normal file
@@ -0,0 +1,117 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace mirzaev\beejee;
|
||||||
|
|
||||||
|
use mirzaev\beejee\core;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Маршрутизатор
|
||||||
|
*
|
||||||
|
* @package mirzaev\beejee
|
||||||
|
* @author Arsen Mirzaev Tatyano-Muradovich <red@hood.su>
|
||||||
|
*/
|
||||||
|
final class router
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array $router Маршруты
|
||||||
|
*/
|
||||||
|
public static array $routes = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Новый маршрут
|
||||||
|
*
|
||||||
|
* @param string $route Маршрут
|
||||||
|
* @param string $controller Контроллер
|
||||||
|
* @param string|null $method Метод
|
||||||
|
* @param string|null $type Тип
|
||||||
|
* @param string|null $model Модель
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function create(string $route, string $controller, string $method = null, string $type = 'GET', string $model = null): void
|
||||||
|
{
|
||||||
|
if (is_null($model)) {
|
||||||
|
$model = $controller;
|
||||||
|
}
|
||||||
|
|
||||||
|
self::$routes[$route][$type] = [
|
||||||
|
// Инициализация контроллера с постфиксом
|
||||||
|
'controller' => preg_match('/' . core::controllerPostfix() . '$/i', $controller) ? $controller : $controller . core::controllerPostfix(),
|
||||||
|
'model' => preg_match('/' . core::modelPostfix() . '$/i', $model) ? $model : $model . core::modelPostfix(),
|
||||||
|
'method' => $method ?? '__construct'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка маршрута
|
||||||
|
*
|
||||||
|
* @param string $route Маршрут
|
||||||
|
* @param string $controller Контроллер
|
||||||
|
*
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public static function handle(string $uri = null): void
|
||||||
|
{
|
||||||
|
// Если не передан URI, то взять из данных веб-сервера
|
||||||
|
$uri = $uri ?? $_SERVER['REQUEST_URI'] ?? '';
|
||||||
|
|
||||||
|
// Инициализация URL
|
||||||
|
$url = parse_url($uri, PHP_URL_PATH);
|
||||||
|
|
||||||
|
// Сортировка массива маршрутов от большего ключа к меньшему
|
||||||
|
krsort(self::$routes);
|
||||||
|
|
||||||
|
foreach (self::$routes as $key => $value) {
|
||||||
|
// Если не записан "/" в начале, то записать
|
||||||
|
$route_name = preg_replace('/^([^\/])/', '/$1', $key);
|
||||||
|
|
||||||
|
if (mb_stripos($route_name, $url, 0, "UTF-8") === 0 && mb_strlen($route_name, 'UTF-8') <= mb_strlen($url, 'UTF-8')) {
|
||||||
|
// Если найден маршрут, а так же его длина не меньше длины запрошенного URL
|
||||||
|
$route = $value[$_SERVER["REQUEST_METHOD"] ?? 'GET'];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!empty($route)) {
|
||||||
|
// Если маршрут найден
|
||||||
|
if (class_exists($controller_class = core::namespace() . '\\controllers\\' . $route['controller'])) {
|
||||||
|
// Если найден класс-контроллер маршрута
|
||||||
|
|
||||||
|
$controller = new $controller_class;
|
||||||
|
|
||||||
|
if (empty($response = $controller->{$route['method']}($_REQUEST))) {
|
||||||
|
// Если не получен ответ после обработки контроллера
|
||||||
|
|
||||||
|
// Удаление постфикса для поиска директории
|
||||||
|
$dir = preg_replace('/' . core::controllerPostfix() . '$/i', '', $route['controller']);
|
||||||
|
|
||||||
|
// Отрисовка шаблона по умолчанию
|
||||||
|
$response = $controller->view($dir);
|
||||||
|
}
|
||||||
|
|
||||||
|
echo $response;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
echo self::error();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function error(): ?string
|
||||||
|
{
|
||||||
|
if (
|
||||||
|
class_exists($class = core::namespace() . '\\controllers\\errors' . core::controllerPostfix()) &&
|
||||||
|
method_exists($class, $method = 'error404')
|
||||||
|
) {
|
||||||
|
// Если существует контроллер ошибок и метод-обработчик ответа 404,
|
||||||
|
// то вызвать обработку ответа 404
|
||||||
|
return (new $class(basename($class)))->$method();
|
||||||
|
} else {
|
||||||
|
// Никаких исключений не вызывать, отдать пустую страницу
|
||||||
|
// Либо можно, но отображать в зависимости от включенного дебаг режима
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
44
mirzaev/beejee/system/views/auth/auth_sidebar.html
Normal file
44
mirzaev/beejee/system/views/auth/auth_sidebar.html
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{% if not user %}
|
||||||
|
<form id="sidebar_auth_panel" class="row mt-4 mt-md-0 p-3 bg-white" onsubmit="auth(this); return false;">
|
||||||
|
<div class="form-group w-100">
|
||||||
|
<input id="auth_sidebar_login" class="col form-control" type="text" name="login" placeholder="Логин" required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group w-100">
|
||||||
|
<input id="auth_sidebar_password" type="password" class="col form-control" name="password" placeholder="Пароль"
|
||||||
|
required>
|
||||||
|
</div>
|
||||||
|
<div class="form-group col p-0 mb-0">
|
||||||
|
<button id="sidebar_auth_button" type="submit" class="col btn btn-primary" data-toggle="button">Войти</button>
|
||||||
|
<!-- <button type="submit" class="col mt-2 btn btn-success">Зарегистрироваться</button> -->
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
{% else %}
|
||||||
|
<div id="sidebar_auth_panel" class="row mt-4 mt-md-0 p-3 bg-white">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row">
|
||||||
|
<div class="col p-0">
|
||||||
|
<div class="w-50 px-5 px-md-0 mx-auto">
|
||||||
|
<img class="avatar img-fluid rounded-circle" src="/img/avatar.webp" alt="Аватар">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col p-0">
|
||||||
|
<h4 class="mt-3 text-center">Имя Фамилия</h4>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col text-center p-0">
|
||||||
|
<p class="mt-3"><b>Статус:</b>
|
||||||
|
{% if admin == 1 %}
|
||||||
|
администратор
|
||||||
|
{% else %}
|
||||||
|
пользователь
|
||||||
|
{% endif %}</p>
|
||||||
|
<button type="submit" class="col mt-2 btn btn-dark" onclick="deauth(this)"
|
||||||
|
data-toggle="button">Выход</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
113
mirzaev/beejee/system/views/main/index.html
Normal file
113
mirzaev/beejee/system/views/main/index.html
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="ru">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||||
|
|
||||||
|
<link href="/css/bootstrap/bootstrap.min.css" rel="stylesheet">
|
||||||
|
<link href="/css/main.css" rel="stylesheet">
|
||||||
|
|
||||||
|
<title>Задачи</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-dark">
|
||||||
|
<div class="container">
|
||||||
|
<div class="row mt-sm-4 mt-md-5">
|
||||||
|
<div class="col-md mr-md-3">
|
||||||
|
<div class="row p-4 bg-white">
|
||||||
|
<div class="col">
|
||||||
|
<div class="row p-3">
|
||||||
|
<h3 class="col mb-4 mb-sm-2 tasks_list_title">Список задач</h3>
|
||||||
|
<select class="col-sm-3 py-2 py-sm-0" id="select_sort" onchange="task_sort(this)">
|
||||||
|
<option class="d-none" selected>Сортировка</option>
|
||||||
|
{% if sort == 'id' %}
|
||||||
|
<option value="id" selected>Идентификатор</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="id">Идентификатор</option>
|
||||||
|
{% endif %}
|
||||||
|
{% if sort == 'name' %}
|
||||||
|
<option value="name" selected>Имя</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="name">Имя</option>
|
||||||
|
{% endif %}
|
||||||
|
{% if sort == 'email' %}
|
||||||
|
<option value="email" selected>Почта</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="email">Почта</option>
|
||||||
|
{% endif %}
|
||||||
|
{% if sort == 'task' %}
|
||||||
|
<option value="task" selected>Задание</option>
|
||||||
|
{% else %}
|
||||||
|
<option value="task">Задание</option>
|
||||||
|
{% endif %}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<form id="form_task_create" onsubmit="return false;">
|
||||||
|
<table id="tasks" class="table table-hover table-responsive-sm table">
|
||||||
|
<tbody id="tasks">
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</form>
|
||||||
|
<div class="row mt-4">
|
||||||
|
<a class="col-sm-3 col-md-2 ml-auto text-center btn btn-secondary" id="button_back"
|
||||||
|
onclick="task_read({{ page.previous }})" role="button">Назад</a>
|
||||||
|
<p class="col-2 ml-auto my-2 text-right" id="page_сurrent">{{ page.current }}</p>
|
||||||
|
<p class="my-sm-auto my-2 text-center">/</p>
|
||||||
|
<p class="col-2 mr-auto my-2 text-left" id="pages_сount">{{ page.count }}</p>
|
||||||
|
<a class="col-sm-3 col-md-2 mr-auto text-center btn btn-secondary" id="button_forward"
|
||||||
|
onclick="task_read({{ page.next }})" role="button">Вперёд</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row mt-4 p-4 bg-white">
|
||||||
|
<div class="col p-0">
|
||||||
|
<p id="task_create_warning" class="bg-warning mb-3 py-2 px-3 rounded" style="display: none;">
|
||||||
|
</p>
|
||||||
|
<p id="task_create_success" class="bg-success text-white mb-3 py-2 px-3 rounded" style="display: none;">
|
||||||
|
Задание успешно добавлено!
|
||||||
|
</p>
|
||||||
|
<form id="tasks_create_panel" class="row px-3 needs-validation" onsubmit="return false;"
|
||||||
|
novalidate>
|
||||||
|
<div class="col-sm-2 p-0 mr-sm-3 mb-3 mb-sm-0">
|
||||||
|
<input type="text" class="form-control" id="task_create_panel_name" placeholder="Имя"
|
||||||
|
name="name" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Укажите имя
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 p-0 mr-sm-3 mb-3 mb-sm-0">
|
||||||
|
<input type="email" class="form-control" id="task_create_panel_email"
|
||||||
|
placeholder="Почта" name="email" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Неверный тип почты
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm p-0 mr-sm-3 mb-3 mb-sm-0">
|
||||||
|
<input type="text" class="form-control" id="task_create_panel_task"
|
||||||
|
placeholder="Задание" name="task" required>
|
||||||
|
<div class="invalid-feedback">
|
||||||
|
Укажите задание
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-sm-3 col-md-3 col-lg-2 p-0">
|
||||||
|
<button type="submit" class="btn btn-success w-100"
|
||||||
|
data-toggle="button">Добавить</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="sidebar" class="col-md-3">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<script type="text/javascript" src="/js/auth.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/tasks.js"></script>
|
||||||
|
<script src="https://kit.fontawesome.com/d7e922c226.js" crossorigin="anonymous"></script>
|
||||||
|
<script type="text/javascript" src="https://code.jquery.com/jquery-3.5.1.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/bootstrap/bootstrap.min.js"></script>
|
||||||
|
<script type="text/javascript" src="/js/bootstrap/forms_validator.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
29
mirzaev/beejee/system/views/tasks/list.html
Normal file
29
mirzaev/beejee/system/views/tasks/list.html
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{% for task in tasks %}
|
||||||
|
<tr id="{{ task.id }}">
|
||||||
|
<td class="text-left">
|
||||||
|
{% if admin == 1 %}
|
||||||
|
{% if task.completed == 1 %}
|
||||||
|
<i class="far fa-check-square ml-1 text-dark task_buttons task_status"
|
||||||
|
onclick="task_change_status(this.closest('tr'))"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="far fa-square ml-1 text-dark task_buttons task_status" onclick="task_change_status(this.closest('tr'))"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% else %}
|
||||||
|
{% if task.completed == 1 %}
|
||||||
|
<i class="far fa-check-square ml-1 text-dark task_status"></i>
|
||||||
|
{% else %}
|
||||||
|
<i class="far fa-square ml-1 text-dark task_status"></i>
|
||||||
|
{% endif %}
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
<td class="task_name">{{ task.name }}</td>
|
||||||
|
<td class="task_email">{{ task.email }}</td>
|
||||||
|
<td class="task_task">{{ task.task }}</td>
|
||||||
|
<td class="text-right">
|
||||||
|
{% if admin == 1 %}
|
||||||
|
<i id="update_button_task_{{ task.id }}" class="fas fa-pen mr-1 task_buttons" onclick="task_update(this.closest('tr'))"></i>
|
||||||
|
<i class="fas fa-trash ml-1 task_delete_buttons task_buttons" onclick="task_delete(this.closest('tr'))"></i>
|
||||||
|
{% endif %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
1
mirzaev/beejee/tests/.gitignore
vendored
Normal file
1
mirzaev/beejee/tests/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
__output/
|
146
mirzaev/beejee/tests/MainCest.php
Normal file
146
mirzaev/beejee/tests/MainCest.php
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
<?php
|
||||||
|
class MainCest
|
||||||
|
{
|
||||||
|
public function _before(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
$I->amOnPage('/?');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка существования списка заданий
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function tasksListExists(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Cписок заданий
|
||||||
|
$I->waitForElement('#tasks');
|
||||||
|
|
||||||
|
// Строка с колонкой статуса
|
||||||
|
$I->waitForElement('//table[@id="tasks"]/tbody/tr/td/i[contains(@class, "task_status")]');
|
||||||
|
|
||||||
|
// Строка с колонкой имени
|
||||||
|
$I->waitForElement('//table[@id="tasks"]/tbody/tr/td[@class="task_name"]');
|
||||||
|
|
||||||
|
// Строка с колонкой почты
|
||||||
|
$I->waitForElement('//table[@id="tasks"]/tbody/tr/td[@class="task_email"]');
|
||||||
|
|
||||||
|
// Строка с колонкой задания
|
||||||
|
$I->waitForElement('//table[@id="tasks"]/tbody/tr/td[@class="task_task"]');
|
||||||
|
|
||||||
|
// Строк три
|
||||||
|
// $I->seeNumberOfElements('//table[@id="tasks"]/tbody/tr', 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка существования панели создания задания
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function tasksCreatePanelExists(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Панель создания заданий
|
||||||
|
$I->waitForElement('#tasks_create_panel');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка существования панели авторизации
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function authPanelExists(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Панель авторизации
|
||||||
|
$I->waitForElement('#sidebar_auth_panel');
|
||||||
|
|
||||||
|
// Первая строка с колонкой задания
|
||||||
|
$I->waitForElement('//button[@id="sidebar_auth_button"]');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка вывода ошибки при создании задания без данных
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function createTaskWithoutData(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Отправка формы
|
||||||
|
$I->click('Добавить');
|
||||||
|
|
||||||
|
// Проверка вывода ошибок
|
||||||
|
$I->see('Укажите имя');
|
||||||
|
$I->see('Неверный тип почты');
|
||||||
|
$I->see('Укажите задание');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка вывода ошибки при введении неверного email
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function createTaskWithWrongEmail(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Заполнение полей
|
||||||
|
$I->fillField(['name' => 'name'], 'test');
|
||||||
|
$I->fillField(['name' => 'email'], 'test');
|
||||||
|
$I->fillField(['name' => 'task'], 'test job');
|
||||||
|
|
||||||
|
// Отправка формы
|
||||||
|
$I->click('Добавить');
|
||||||
|
|
||||||
|
// Проверка вывода ошибок
|
||||||
|
$I->dontSee('Укажите имя');
|
||||||
|
$I->see('Неверный тип почты');
|
||||||
|
$I->dontSee('Укажите задание');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Проверка вывода ошибки при введении неверного email
|
||||||
|
*
|
||||||
|
* @param AcceptanceTester $I
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
public function createTask(AcceptanceTester $I)
|
||||||
|
{
|
||||||
|
// Заполнение полей
|
||||||
|
$I->fillField(['name' => 'name'], 'test');
|
||||||
|
$I->fillField(['name' => 'email'], 'test@test.test');
|
||||||
|
$I->fillField(['name' => 'task'], 'test job');
|
||||||
|
|
||||||
|
// Проверка вывода ошибок
|
||||||
|
$I->dontSee('Укажите имя');
|
||||||
|
$I->dontSee('Неверный тип почты');
|
||||||
|
$I->dontSee('Укажите задание');
|
||||||
|
|
||||||
|
// Отправка формы
|
||||||
|
$I->click('Добавить');
|
||||||
|
|
||||||
|
$I->waitForText('Задание успешно добавлено!');
|
||||||
|
|
||||||
|
$page = $I->grabTextFrom('//p[@id="pages_сount"]');
|
||||||
|
|
||||||
|
$I->openNewTab($I->executeJS("return location.protocol + '//' + location.host + location.pathname") . '?page=' . $page . '&sort=id');
|
||||||
|
|
||||||
|
$I->waitForElement('//p[@id="page_сurrent"]');
|
||||||
|
$current_page = $I->grabTextFrom('//p[@id="page_сurrent"]');
|
||||||
|
|
||||||
|
// Проверка на то, что открыта последняя страница
|
||||||
|
if ($page != $current_page) {
|
||||||
|
throw new Exception('Не удалось открыть последнюю страницу');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Строка с колонкой имени
|
||||||
|
echo $name = $I->grabTextFrom('//table[@id="tasks"]/tbody/tr/td[@class="task_name"]');
|
||||||
|
|
||||||
|
// Строка с колонкой почты
|
||||||
|
echo $email = $I->grabTextFrom('//table[@id="tasks"]/tbody/tr/td[@class="task_email"]');
|
||||||
|
|
||||||
|
// Строка с колонкой задания
|
||||||
|
echo $task = $I->grabTextFrom('//table[@id="tasks"]/tbody/tr/td[@class="task_task"]');
|
||||||
|
}
|
||||||
|
}
|
0
mirzaev/beejee/tests/_data/.gitkeep
Normal file
0
mirzaev/beejee/tests/_data/.gitkeep
Normal file
0
mirzaev/beejee/tests/_output/.gitkeep
Normal file
0
mirzaev/beejee/tests/_output/.gitkeep
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<html><head></head><body></body></html>
|
BIN
mirzaev/beejee/tests/_output/MainCest.createTask.fail.png
Normal file
BIN
mirzaev/beejee/tests/_output/MainCest.createTask.fail.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.6 KiB |
File diff suppressed because one or more lines are too long
Binary file not shown.
After Width: | Height: | Size: 17 KiB |
6
mirzaev/beejee/tests/_output/failed
Normal file
6
mirzaev/beejee/tests/_output/failed
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
mirzaev\beejee\tests\MainCest.php:tasksListExists
|
||||||
|
mirzaev\beejee\tests\MainCest.php:tasksCreatePanelExists
|
||||||
|
mirzaev\beejee\tests\MainCest.php:authPanelExists
|
||||||
|
mirzaev\beejee\tests\MainCest.php:createTaskWithoutData
|
||||||
|
mirzaev\beejee\tests\MainCest.php:createTaskWithWrongEmail
|
||||||
|
mirzaev\beejee\tests\MainCest.php:createTask
|
26
mirzaev/beejee/tests/_support/AcceptanceTester.php
Normal file
26
mirzaev/beejee/tests/_support/AcceptanceTester.php
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inherited Methods
|
||||||
|
* @method void wantToTest($text)
|
||||||
|
* @method void wantTo($text)
|
||||||
|
* @method void execute($callable)
|
||||||
|
* @method void expectTo($prediction)
|
||||||
|
* @method void expect($prediction)
|
||||||
|
* @method void amGoingTo($argumentation)
|
||||||
|
* @method void am($role)
|
||||||
|
* @method void lookForwardTo($achieveValue)
|
||||||
|
* @method void comment($description)
|
||||||
|
* @method void pause()
|
||||||
|
*
|
||||||
|
* @SuppressWarnings(PHPMD)
|
||||||
|
*/
|
||||||
|
class AcceptanceTester extends \Codeception\Actor
|
||||||
|
{
|
||||||
|
use _generated\AcceptanceTesterActions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define custom actions here
|
||||||
|
*/
|
||||||
|
}
|
10
mirzaev/beejee/tests/_support/Helper/Acceptance.php
Normal file
10
mirzaev/beejee/tests/_support/Helper/Acceptance.php
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Helper;
|
||||||
|
|
||||||
|
// here you can define custom actions
|
||||||
|
// all public methods declared in helper class will be available in $I
|
||||||
|
|
||||||
|
class Acceptance extends \Codeception\Module
|
||||||
|
{
|
||||||
|
}
|
6161
mirzaev/beejee/tests/_support/_generated/AcceptanceTesterActions.php
Normal file
6161
mirzaev/beejee/tests/_support/_generated/AcceptanceTesterActions.php
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user