Первая инициализация
This commit is contained in:
commit
d66f02294a
|
@ -0,0 +1,2 @@
|
|||
vendor/
|
||||
cache/
|
|
@ -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
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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';
|
||||
}
|
||||
}
|
|
@ -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]);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
# ----------------------------
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
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,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
|
@ -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
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
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,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;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.3 KiB |
|
@ -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');
|
|
@ -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();
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because it is too large
Load Diff
File diff suppressed because one or more lines are too long
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 @@
|
|||
|
|
@ -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();
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 %}
|
|
@ -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>
|
|
@ -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 %}
|
|
@ -0,0 +1 @@
|
|||
__output/
|
|
@ -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,0 +1 @@
|
|||
<html><head></head><body></body></html>
|
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 |
|
@ -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
|
|
@ -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
|
||||
*/
|
||||
}
|
|
@ -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
|
||||
{
|
||||
}
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue