Контроллер аутентификации
PHP:
<?php
// app/Controllers/AuthController.php
namespace App\Controllers;
use App\Core\Controller;
use App\Models\User;
class AuthController extends Controller
{
public function loginForm()
{
if ($this->getCurrentUser()) {
$this->redirect('/tasks');
}
$this->render('auth/login');
}
public function login()
{
$errors = $this->validate($_POST, [
'username' => 'required',
'password' => 'required'
]);
if (!empty($errors)) {
$this->render('auth/login', [
'errors' => $errors,
'old' => $_POST
]);
return;
}
$user = User::authenticate($_POST['username'], $_POST['password']);
if ($user) {
session_start();
$_SESSION['user'] = $user->toArray();
if (isset($_POST['remember'])) {
setcookie('user_id', $user->id, time() + 86400 * 30, '/');
}
$this->redirect('/tasks');
} else {
$this->render('auth/login', [
'error' => 'Неверное имя пользователя или пароль',
'old' => $_POST
]);
}
}
public function registerForm()
{
if ($this->getCurrentUser()) {
$this->redirect('/tasks');
}
$this->render('auth/register');
}
public function register()
{
$errors = $this->validate($_POST, [
'username' => 'required|min:3',
'email' => 'required|email',
'password' => 'required|min:6',
'password_confirmation' => 'required'
]);
if ($_POST['password'] !== $_POST['password_confirmation']) {
$errors['password_confirmation'][] = 'Пароли не совпадают';
}
// Проверка уникальности
$existingUser = User::where('username', '=', $_POST['username'])->get();
if (!empty($existingUser)) {
$errors['username'][] = 'Имя пользователя уже занято';
}
$existingEmail = User::where('email', '=', $_POST['email'])->get();
if (!empty($existingEmail)) {
$errors['email'][] = 'Email уже зарегистрирован';
}
if (!empty($errors)) {
$this->render('auth/register', [
'errors' => $errors,
'old' => $_POST
]);
return;
}
if (User::register($_POST['username'], $_POST['email'], $_POST['password'])) {
$_SESSION['success'] = 'Регистрация прошла успешно! Войдите в систему.';
$this->redirect('/login');
} else {
$this->render('auth/register', [
'error' => 'Ошибка при регистрации',
'old' => $_POST
]);
}
}
public function logout()
{
session_start();
session_destroy();
setcookie('user_id', '', time() - 3600, '/');
$this->redirect('/login');
}
}
5. Разработка представлений
Базовый шаблон
PHP:
<?php
// app/Views/layouts/main.php
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>To-Do List - <?= $title ?? 'Главная' ?></title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.8.1/font/bootstrap-icons.css" rel="stylesheet">
<link href="/public/css/style.css" rel="stylesheet">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/tasks">
<i class="bi bi-check2-square"></i> To-Do List
</a>
<?php if ($user = \App\Core\Controller::getCurrentUser()): ?>
<div class="navbar-collapse">
<ul class="navbar-nav me-auto">
<li class="nav-item">
<a class="nav-link" href="/tasks">
<i class="bi bi-list-task"></i> Мои задачи
</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/tasks/create">
<i class="bi bi-plus-circle"></i> Новая задача
</a>
</li>
</ul>
<div class="navbar-text me-3">
Привет, <strong><?= htmlspecialchars($user['username']) ?></strong>!
</div>
<a href="/logout" class="btn btn-outline-light btn-sm">
<i class="bi bi-box-arrow-right"></i> Выйти
</a>
</div>
<?php endif; ?>
</div>
</nav>
<main class="container mt-4">
<?php if (isset($_SESSION['success'])): ?>
<div class="alert alert-success alert-dismissible fade show" role="alert">
<?= $_SESSION['success'] ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php unset($_SESSION['success']); ?>
<?php endif; ?>
<?php if (isset($_SESSION['error'])): ?>
<div class="alert alert-danger alert-dismissible fade show" role="alert">
<?= $_SESSION['error'] ?>
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
</div>
<?php unset($_SESSION['error']); ?>
<?php endif; ?>
<?= $content ?>
</main>
<footer class="bg-light mt-5 py-3">
<div class="container text-center text-muted">
<small>To-Do List © <?= date('Y') ?></small>
</div>
</footer>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script src="/public/js/app.js"></script>
</body>
</html>
Страница списка задач
PHP:
<?php
// app/Views/tasks/index.php
ob_start();
?>
<div class="row">
<div class="col-md-3">
<div class="card mb-4">
<div class="card-header">
<h5 class="card-title mb-0">Статистика</h5>
</div>
<div class="card-body">
<div class="mb-3">
<span class="badge bg-primary rounded-pill">Всего: <?= $statistics['total'] ?></span>
</div>
<div class="mb-2">
<span class="badge bg-success">Завершено: <?= $statistics['completed'] ?></span>
</div>
<div class="mb-2">
<span class="badge bg-warning">В работе: <?= $statistics['in_progress'] ?></span>
</div>
<div class="mb-2">
<span class="badge bg-secondary">В ожидании: <?= $statistics['pending'] ?></span>
</div>
<?php if ($statistics['overdue'] > 0): ?>
<div class="mb-2">
<span class="badge bg-danger">Просрочено: <?= $statistics['overdue'] ?></span>
</div>
<?php endif; ?>
</div>
</div>
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Фильтры</h5>
</div>
<div class="card-body">
<form method="get" action="/tasks">
<div class="mb-3">
<label class="form-label">Статус</label>
<select class="form-select" name="status">
<option value="">Все</option>
<option value="pending" <?= $filters['status'] == 'pending' ? 'selected' : '' ?>>В ожидании</option>
<option value="in_progress" <?= $filters['status'] == 'in_progress' ? 'selected' : '' ?>>В работе</option>
<option value="completed" <?= $filters['status'] == 'completed' ? 'selected' : '' ?>>Завершено</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Приоритет</label>
<select class="form-select" name="priority">
<option value="">Все</option>
<option value="low" <?= $filters['priority'] == 'low' ? 'selected' : '' ?>>Низкий</option>
<option value="medium" <?= $filters['priority'] == 'medium' ? 'selected' : '' ?>>Средний</option>
<option value="high" <?= $filters['priority'] == 'high' ? 'selected' : '' ?>>Высокий</option>
</select>
</div>
<div class="mb-3">
<label class="form-label">Метка</label>
<select class="form-select" name="tag_id">
<option value="">Все</option>
<?php foreach ($tags as $tag): ?>
<option value="<?= $tag->id ?>" <?= $filters['tag_id'] == $tag->id ? 'selected' : '' ?>>
<?= htmlspecialchars($tag->name) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<button type="submit" class="btn btn-primary w-100">Применить</button>
<a href="/tasks" class="btn btn-secondary w-100 mt-2">Сбросить</a>
</form>
</div>
</div>
</div>
<div class="col-md-9">
<div class="d-flex justify-content-between align-items-center mb-4">
<h1>Мои задачи</h1>
<div class="d-flex">
<div class="input-group me-2" style="width: 300px;">
<input type="text" class="form-control" id="search-input" placeholder="Поиск задач...">
<button class="btn btn-outline-secondary" type="button" id="search-button">
<i class="bi bi-search"></i>
</button>
</div>
<a href="/tasks/create" class="btn btn-success">
<i class="bi bi-plus-circle"></i> Новая задача
</a>
</div>
</div>
<div id="search-results" class="mb-4" style="display: none;">
<div class="card">
<div class="card-header">
<h5 class="card-title mb-0">Результаты поиска</h5>
</div>
<div class="card-body" id="search-results-body">
<!-- Результаты поиска будут загружены через AJAX -->
</div>
</div>
</div>
<?php if (empty($tasks)): ?>
<div class="alert alert-info">
<i class="bi bi-info-circle"></i> У вас пока нет задач. <a href="/tasks/create">Создайте первую задачу</a>.
</div>
<?php else: ?>
<div class="table-responsive">
<table class="table table-hover">
<thead class="table-light">
<tr>
<th width="50">#</th>
<th>Задача</th>
<th width="120">Статус</th>
<th width="120">Приоритет</th>
<th width="120">Срок</th>
<th width="150">Метки</th>
<th width="150">Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($tasks as $task): ?>
<tr class="<?= $task->isOverdue() ? 'table-danger' : '' ?>">
<td><?= $task->id ?></td>
<td>
<a href="/tasks/<?= $task->id ?>" class="text-decoration-none">
<strong><?= htmlspecialchars($task->title) ?></strong>
</a>
<?php if ($task->isOverdue()): ?>
<span class="badge bg-danger ms-2">Просрочено</span>
<?php endif; ?>
<div class="text-muted small mt-1">
<?= nl2br(htmlspecialchars(mb_substr($task->description, 0, 100))) ?>...
</div>
</td>
<td>
<div class="status-select" data-task-id="<?= $task->id ?>">
<?= $task->getStatusBadge() ?>
</div>
</td>
<td><?= $task->getPriorityBadge() ?></td>
<td>
<?php if ($task->due_date): ?>
<?= date('d.m.Y', strtotime($task->due_date)) ?>
<?php else: ?>
<span class="text-muted">—</span>
<?php endif; ?>
</td>
<td>
<?php foreach ($task->tags() as $tag): ?>
<span class="badge me-1" <?= $tag->getColorStyle() ?>>
<?= htmlspecialchars($tag->name) ?>
</span>
<?php endforeach; ?>
</td>
<td>
<div class="btn-group btn-group-sm">
<a href="/tasks/<?= $task->id ?>" class="btn btn-outline-primary">
<i class="bi bi-eye"></i>
</a>
<a href="/tasks/<?= $task->id ?>/edit" class="btn btn-outline-warning">
<i class="bi bi-pencil"></i>
</a>
<button type="button" class="btn btn-outline-danger delete-task"
data-id="<?= $task->id ?>"
data-title="<?= htmlspecialchars($task->title) ?>">
<i class="bi bi-trash"></i>
</button>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
<div class="modal fade" id="statusModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Изменить статус</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<input type="hidden" id="task-id">
<div class="mb-3">
<label class="form-label">Новый статус</label>
<select class="form-select" id="new-status">
<option value="pending">В ожидании</option>
<option value="in_progress">В работе</option>
<option value="completed">Завершено</option>
</select>
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<button type="button" class="btn btn-primary" id="save-status">Сохранить</button>
</div>
</div>
</div>
</div>
<div class="modal fade" id="deleteModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">Подтверждение удаления</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body">
<p>Вы уверены, что хотите удалить задачу "<span id="task-title"></span>"?</p>
<p class="text-danger"><small>Это действие нельзя отменить.</small></p>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Отмена</button>
<form id="delete-form" method="post" style="display: inline;">
<?php // CSRF токен должен быть добавлен в реальном проекте ?>
<button type="submit" class="btn btn-danger">Удалить</button>
</form>
</div>
</div>
</div>
</div>
<?php
$content = ob_get_clean();
$title = 'Мои задачи';
include __DIR__ . '/../layouts/main.php';
?>