[MVC] Полная миграция на MVC архитектуру

- Создано ядро MVC: App, Router, Controller, Model, View, Database
- Созданы модели: User, Product, Category, Cart, Order
- Созданы контроллеры: Home, Auth, Product, Cart, Order, Page, Admin
- Созданы layouts и partials для представлений
- Добавлены все views для страниц
- Настроена маршрутизация с чистыми URL
- Обновлена конфигурация Docker и Apache для mod_rewrite
- Добавлена единая точка входа public/index.php
This commit is contained in:
kirill.khorkov
2026-01-03 11:48:14 +03:00
parent 3f257120fa
commit d2c15ec37f
53 changed files with 8650 additions and 30 deletions

View File

@@ -0,0 +1,56 @@
<?php $isEdit = $action === 'edit'; ?>
<h2><?= $isEdit ? 'Редактирование категории' : 'Добавление категории' ?></h2>
<a href="/cite_practica/admin/categories" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к списку
</a>
<div class="form-container">
<form action="/cite_practica/admin/categories/<?= $isEdit ? 'edit/' . $category['category_id'] : 'add' ?>" method="POST">
<div class="form-group">
<label>Название категории *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($category['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Родительская категория</label>
<select name="parent_id" class="form-control">
<option value="">Нет (корневая категория)</option>
<?php foreach ($parentCategories as $parent): ?>
<?php if (!$isEdit || $parent['category_id'] != $category['category_id']): ?>
<option value="<?= $parent['category_id'] ?>"
<?= ($category['parent_id'] ?? 0) == $parent['category_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($parent['name']) ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($category['description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label>Порядок сортировки</label>
<input type="number" name="sort_order" class="form-control"
value="<?= $category['sort_order'] ?? 0 ?>">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" value="1"
<?= ($category['is_active'] ?? true) ? 'checked' : '' ?>>
Категория активна
</label>
</div>
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> <?= $isEdit ? 'Сохранить изменения' : 'Добавить категорию' ?>
</button>
</form>
</div>

View File

@@ -0,0 +1,60 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление категориями</h2>
<a href="/cite_practica/admin/categories/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить категорию
</a>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Родитель</th>
<th>Товаров</th>
<th>Порядок</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $category): ?>
<tr style="<?= !$category['is_active'] ? 'opacity: 0.5;' : '' ?>">
<td><?= $category['category_id'] ?></td>
<td><?= htmlspecialchars($category['name']) ?></td>
<td><?= htmlspecialchars($category['parent_name'] ?? '-') ?></td>
<td><?= $category['product_count'] ?></td>
<td><?= $category['sort_order'] ?></td>
<td>
<?php if ($category['is_active']): ?>
<span style="color: green;"><i class="fas fa-check"></i> Активна</span>
<?php else: ?>
<span style="color: red;"><i class="fas fa-times"></i> Скрыта</span>
<?php endif; ?>
</td>
<td>
<div class="action-buttons">
<a href="/cite_practica/admin/categories/edit/<?= $category['category_id'] ?>" class="btn btn-sm btn-warning" title="Редактировать">
<i class="fas fa-edit"></i>
</a>
<form action="/cite_practica/admin/categories/delete/<?= $category['category_id'] ?>" method="POST" style="display: inline;"
onsubmit="return confirm('Удалить категорию?');">
<button type="submit" class="btn btn-sm btn-danger" title="Удалить">
<i class="fas fa-trash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,45 @@
<?php $action = 'dashboard'; ?>
<h2>Дашборд</h2>
<div class="stats-grid">
<div class="stat-card">
<h3><?= $stats['total_products'] ?></h3>
<p>Всего товаров</p>
</div>
<div class="stat-card">
<h3><?= $stats['active_products'] ?></h3>
<p>Активных товаров</p>
</div>
<div class="stat-card">
<h3><?= $stats['total_orders'] ?></h3>
<p>Заказов</p>
</div>
<div class="stat-card">
<h3><?= $stats['total_users'] ?></h3>
<p>Пользователей</p>
</div>
<div class="stat-card">
<h3><?= number_format($stats['revenue'], 0, '', ' ') ?> ₽</h3>
<p>Выручка</p>
</div>
</div>
<div style="margin-top: 30px;">
<h3>Быстрые действия</h3>
<div style="display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap;">
<a href="/cite_practica/admin/products/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить товар
</a>
<a href="/cite_practica/admin/categories/add" class="btn btn-primary">
<i class="fas fa-plus"></i> Добавить категорию
</a>
<a href="/cite_practica/admin/orders" class="btn btn-primary">
<i class="fas fa-shopping-cart"></i> Просмотреть заказы
</a>
<a href="/cite_practica/catalog" class="btn btn-primary">
<i class="fas fa-store"></i> Перейти в каталог
</a>
</div>
</div>

View File

@@ -0,0 +1,74 @@
<?php use App\Core\View; ?>
<a href="/cite_practica/admin/orders" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к заказам
</a>
<h2>Заказ <?= htmlspecialchars($order['order_number']) ?></h2>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px;">
<div class="form-container">
<h3>Информация о заказе</h3>
<table style="border: none;">
<tr><td><strong>Дата:</strong></td><td><?= View::formatDateTime($order['created_at']) ?></td></tr>
<tr><td><strong>Покупатель:</strong></td><td><?= htmlspecialchars($order['customer_name']) ?></td></tr>
<tr><td><strong>Email:</strong></td><td><?= htmlspecialchars($order['customer_email']) ?></td></tr>
<tr><td><strong>Телефон:</strong></td><td><?= htmlspecialchars($order['customer_phone']) ?></td></tr>
<tr><td><strong>Адрес:</strong></td><td><?= htmlspecialchars($order['delivery_address']) ?></td></tr>
<tr><td><strong>Способ доставки:</strong></td><td><?= htmlspecialchars($order['delivery_method']) ?></td></tr>
<tr><td><strong>Способ оплаты:</strong></td><td><?= htmlspecialchars($order['payment_method']) ?></td></tr>
</table>
</div>
<div class="form-container">
<h3>Статус заказа</h3>
<form action="/cite_practica/admin/orders/<?= $order['order_id'] ?>/status" method="POST">
<div class="form-group">
<select name="status" class="form-control">
<option value="pending" <?= $order['status'] === 'pending' ? 'selected' : '' ?>>Ожидает</option>
<option value="processing" <?= $order['status'] === 'processing' ? 'selected' : '' ?>>В обработке</option>
<option value="shipped" <?= $order['status'] === 'shipped' ? 'selected' : '' ?>>Отправлен</option>
<option value="completed" <?= $order['status'] === 'completed' ? 'selected' : '' ?>>Завершен</option>
<option value="cancelled" <?= $order['status'] === 'cancelled' ? 'selected' : '' ?>>Отменен</option>
</select>
</div>
<button type="submit" class="btn btn-success">Обновить статус</button>
</form>
<h3 style="margin-top: 20px;">Итого</h3>
<table style="border: none;">
<tr><td>Товары:</td><td><?= View::formatPrice($order['subtotal']) ?></td></tr>
<tr><td>Скидка:</td><td>-<?= View::formatPrice($order['discount_amount']) ?></td></tr>
<tr><td>Доставка:</td><td><?= View::formatPrice($order['delivery_price']) ?></td></tr>
<tr style="font-weight: bold;"><td>ИТОГО:</td><td><?= View::formatPrice($order['final_amount']) ?></td></tr>
</table>
</div>
</div>
<h3 style="margin-top: 30px;">Товары в заказе</h3>
<table>
<thead>
<tr>
<th>Изображение</th>
<th>Товар</th>
<th>Цена</th>
<th>Кол-во</th>
<th>Сумма</th>
</tr>
</thead>
<tbody>
<?php foreach ($order['items'] as $item): ?>
<tr>
<td>
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td>
<td><?= htmlspecialchars($item['product_name']) ?></td>
<td><?= View::formatPrice($item['product_price']) ?></td>
<td><?= $item['quantity'] ?></td>
<td><?= View::formatPrice($item['total_price']) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,62 @@
<?php use App\Core\View; ?>
<h2>Заказы</h2>
<?php if (empty($orders)): ?>
<div class="alert">Заказы отсутствуют</div>
<?php else: ?>
<table>
<thead>
<tr>
<th>№ заказа</th>
<th>Дата</th>
<th>Покупатель</th>
<th>Сумма</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($orders as $order): ?>
<tr>
<td><strong><?= htmlspecialchars($order['order_number']) ?></strong></td>
<td><?= View::formatDateTime($order['created_at']) ?></td>
<td>
<?= htmlspecialchars($order['customer_name']) ?><br>
<small><?= htmlspecialchars($order['user_email']) ?></small>
</td>
<td><?= View::formatPrice($order['final_amount']) ?></td>
<td>
<?php
$statusColors = [
'pending' => '#ffc107',
'processing' => '#17a2b8',
'shipped' => '#007bff',
'completed' => '#28a745',
'cancelled' => '#dc3545'
];
$statusNames = [
'pending' => 'Ожидает',
'processing' => 'Обработка',
'shipped' => 'Отправлен',
'completed' => 'Завершен',
'cancelled' => 'Отменен'
];
$color = $statusColors[$order['status']] ?? '#666';
$name = $statusNames[$order['status']] ?? $order['status'];
?>
<span style="background: <?= $color ?>; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<?= $name ?>
</span>
</td>
<td>
<a href="/cite_practica/admin/orders/<?= $order['order_id'] ?>" class="btn btn-sm btn-primary">
<i class="fas fa-eye"></i> Подробнее
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>

View File

@@ -0,0 +1,106 @@
<?php $isEdit = $action === 'edit'; ?>
<h2><?= $isEdit ? 'Редактирование товара' : 'Добавление товара' ?></h2>
<a href="/cite_practica/admin/products" class="btn btn-primary" style="margin-bottom: 20px;">
<i class="fas fa-arrow-left"></i> Назад к списку
</a>
<div class="form-container">
<form action="/cite_practica/admin/products/<?= $isEdit ? 'edit/' . $product['product_id'] : 'add' ?>" method="POST">
<div class="form-group">
<label>Название товара *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($product['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Категория *</label>
<select name="category_id" class="form-control" required>
<option value="">Выберите категорию</option>
<?php foreach ($categories as $cat): ?>
<option value="<?= $cat['category_id'] ?>"
<?= ($product['category_id'] ?? 0) == $cat['category_id'] ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="4"><?= htmlspecialchars($product['description'] ?? '') ?></textarea>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Цена *</label>
<input type="number" name="price" class="form-control" step="0.01"
value="<?= $product['price'] ?? '' ?>" required>
</div>
<div class="form-group">
<label>Старая цена (для скидки)</label>
<input type="number" name="old_price" class="form-control" step="0.01"
value="<?= $product['old_price'] ?? '' ?>">
</div>
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Артикул (SKU)</label>
<input type="text" name="sku" class="form-control"
value="<?= htmlspecialchars($product['sku'] ?? '') ?>"
placeholder="Автоматически, если пусто">
</div>
<div class="form-group">
<label>Количество на складе *</label>
<input type="number" name="stock_quantity" class="form-control"
value="<?= $product['stock_quantity'] ?? 0 ?>" required>
</div>
</div>
<div class="form-group">
<label>URL изображения</label>
<input type="text" name="image_url" class="form-control"
value="<?= htmlspecialchars($product['image_url'] ?? '') ?>"
placeholder="img/название.jpg">
</div>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 15px;">
<div class="form-group">
<label>Цвет</label>
<input type="text" name="color" class="form-control"
value="<?= htmlspecialchars($product['color'] ?? '') ?>">
</div>
<div class="form-group">
<label>Материал</label>
<input type="text" name="material" class="form-control"
value="<?= htmlspecialchars($product['material'] ?? '') ?>">
</div>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_available" value="1"
<?= ($product['is_available'] ?? true) ? 'checked' : '' ?>>
Товар доступен для покупки
</label>
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_featured" value="1"
<?= ($product['is_featured'] ?? false) ? 'checked' : '' ?>>
Рекомендуемый товар
</label>
</div>
<button type="submit" class="btn btn-success">
<i class="fas fa-save"></i> <?= $isEdit ? 'Сохранить изменения' : 'Добавить товар' ?>
</button>
</form>
</div>

View File

@@ -0,0 +1,75 @@
<?php use App\Core\View; ?>
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление товарами</h2>
<a href="/cite_practica/admin/products/add" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить товар
</a>
</div>
<?php if (!empty($message)): ?>
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
<?php endif; ?>
<?php if (!empty($error)): ?>
<div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
<?php endif; ?>
<div style="margin-bottom: 15px;">
<a href="/cite_practica/admin/products" class="btn btn-sm <?= !$showAll ? 'btn-primary' : '' ?>">Активные</a>
<a href="/cite_practica/admin/products?show_all=1" class="btn btn-sm <?= $showAll ? 'btn-primary' : '' ?>">Все товары</a>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Изображение</th>
<th>Название</th>
<th>Категория</th>
<th>Цена</th>
<th>На складе</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($products as $product): ?>
<tr style="<?= !$product['is_available'] ? 'opacity: 0.5;' : '' ?>">
<td><?= $product['product_id'] ?></td>
<td>
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td>
<td><?= htmlspecialchars($product['name']) ?></td>
<td><?= htmlspecialchars($product['category_name'] ?? 'Без категории') ?></td>
<td><?= View::formatPrice($product['price']) ?></td>
<td><?= $product['stock_quantity'] ?> шт.</td>
<td>
<?php if ($product['is_available']): ?>
<span style="color: green;"><i class="fas fa-check"></i> Активен</span>
<?php else: ?>
<span style="color: red;"><i class="fas fa-times"></i> Скрыт</span>
<?php endif; ?>
</td>
<td>
<div class="action-buttons">
<a href="/cite_practica/product/<?= $product['product_id'] ?>" class="btn btn-sm btn-primary" title="Просмотр">
<i class="fas fa-eye"></i>
</a>
<a href="/cite_practica/admin/products/edit/<?= $product['product_id'] ?>" class="btn btn-sm btn-warning" title="Редактировать">
<i class="fas fa-edit"></i>
</a>
<form action="/cite_practica/admin/products/delete/<?= $product['product_id'] ?>" method="POST" style="display: inline;"
onsubmit="return confirm('Скрыть товар?');">
<button type="submit" class="btn btn-sm btn-danger" title="Скрыть">
<i class="fas fa-eye-slash"></i>
</button>
</form>
</div>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>

View File

@@ -0,0 +1,43 @@
<?php use App\Core\View; ?>
<h2>Пользователи</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>ФИО</th>
<th>Email</th>
<th>Телефон</th>
<th>Город</th>
<th>Роль</th>
<th>Регистрация</th>
<th>Последний вход</th>
</tr>
</thead>
<tbody>
<?php foreach ($users as $u): ?>
<tr>
<td><?= $u['user_id'] ?></td>
<td><?= htmlspecialchars($u['full_name'] ?? '-') ?></td>
<td><?= htmlspecialchars($u['email']) ?></td>
<td><?= htmlspecialchars($u['phone'] ?? '-') ?></td>
<td><?= htmlspecialchars($u['city'] ?? '-') ?></td>
<td>
<?php if ($u['is_admin']): ?>
<span style="background: #617365; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<i class="fas fa-user-shield"></i> Админ
</span>
<?php else: ?>
<span style="background: #28a745; color: white; padding: 3px 10px; border-radius: 4px; font-size: 12px;">
<i class="fas fa-user"></i> Пользователь
</span>
<?php endif; ?>
</td>
<td><?= $u['created_at'] ? View::formatDateTime($u['created_at']) : '-' ?></td>
<td><?= $u['last_login'] ? View::formatDateTime($u['last_login']) : 'Не было' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>