Fix LESS import error and refactor project structure
This commit is contained in:
@@ -8,9 +8,6 @@ use App\Models\Category;
|
||||
use App\Models\Order;
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* AdminController - контроллер админ-панели
|
||||
*/
|
||||
class AdminController extends Controller
|
||||
{
|
||||
private Product $productModel;
|
||||
@@ -26,9 +23,6 @@ class AdminController extends Controller
|
||||
$this->userModel = new User();
|
||||
}
|
||||
|
||||
/**
|
||||
* Дашборд
|
||||
*/
|
||||
public function dashboard(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -47,11 +41,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
// ========== Товары ==========
|
||||
|
||||
/**
|
||||
* Список товаров
|
||||
*/
|
||||
public function products(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -68,9 +57,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма добавления товара
|
||||
*/
|
||||
public function addProduct(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -85,9 +71,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранение нового товара
|
||||
*/
|
||||
public function storeProduct(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -116,9 +99,6 @@ class AdminController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма редактирования товара
|
||||
*/
|
||||
public function editProduct(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -140,9 +120,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление товара
|
||||
*/
|
||||
public function updateProduct(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -168,9 +145,6 @@ class AdminController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление товара (делаем недоступным)
|
||||
*/
|
||||
public function deleteProduct(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -179,11 +153,6 @@ class AdminController extends Controller
|
||||
$this->redirect('/admin/products?message=' . urlencode('Товар скрыт'));
|
||||
}
|
||||
|
||||
// ========== Категории ==========
|
||||
|
||||
/**
|
||||
* Список категорий
|
||||
*/
|
||||
public function categories(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -198,9 +167,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма добавления категории
|
||||
*/
|
||||
public function addCategory(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -215,9 +181,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Сохранение категории
|
||||
*/
|
||||
public function storeCategory(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -238,9 +201,6 @@ class AdminController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма редактирования категории
|
||||
*/
|
||||
public function editCategory(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -262,9 +222,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление категории
|
||||
*/
|
||||
public function updateCategory(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -285,9 +242,6 @@ class AdminController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удаление категории
|
||||
*/
|
||||
public function deleteCategory(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -304,11 +258,6 @@ class AdminController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Заказы ==========
|
||||
|
||||
/**
|
||||
* Список заказов
|
||||
*/
|
||||
public function orders(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -321,9 +270,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Детали заказа
|
||||
*/
|
||||
public function orderDetails(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -341,9 +287,6 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновление статуса заказа
|
||||
*/
|
||||
public function updateOrderStatus(int $id): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -354,11 +297,6 @@ class AdminController extends Controller
|
||||
$this->redirect('/admin/orders/' . $id);
|
||||
}
|
||||
|
||||
// ========== Пользователи ==========
|
||||
|
||||
/**
|
||||
* Список пользователей
|
||||
*/
|
||||
public function users(): void
|
||||
{
|
||||
$this->requireAdmin();
|
||||
@@ -371,4 +309,3 @@ class AdminController extends Controller
|
||||
], 'admin');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,9 +5,6 @@ namespace App\Controllers;
|
||||
use App\Core\Controller;
|
||||
use App\Models\User;
|
||||
|
||||
/**
|
||||
* AuthController - контроллер авторизации
|
||||
*/
|
||||
class AuthController extends Controller
|
||||
{
|
||||
private User $userModel;
|
||||
@@ -17,9 +14,6 @@ class AuthController extends Controller
|
||||
$this->userModel = new User();
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма входа
|
||||
*/
|
||||
public function loginForm(): void
|
||||
{
|
||||
if ($this->isAuthenticated()) {
|
||||
@@ -35,9 +29,6 @@ class AuthController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка входа
|
||||
*/
|
||||
public function login(): void
|
||||
{
|
||||
$email = $this->getPost('email', '');
|
||||
@@ -62,7 +53,6 @@ class AuthController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Устанавливаем сессию
|
||||
$this->setSession($user);
|
||||
|
||||
$this->json([
|
||||
@@ -71,9 +61,6 @@ class AuthController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Форма регистрации
|
||||
*/
|
||||
public function registerForm(): void
|
||||
{
|
||||
if ($this->isAuthenticated()) {
|
||||
@@ -86,15 +73,11 @@ class AuthController extends Controller
|
||||
'success' => $_SESSION['registration_success'] ?? null
|
||||
]);
|
||||
|
||||
// Очищаем flash данные
|
||||
unset($_SESSION['registration_errors']);
|
||||
unset($_SESSION['old_data']);
|
||||
unset($_SESSION['registration_success']);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка регистрации
|
||||
*/
|
||||
public function register(): void
|
||||
{
|
||||
$errors = [];
|
||||
@@ -107,7 +90,6 @@ class AuthController extends Controller
|
||||
$confirmPassword = $this->getPost('confirm-password', '');
|
||||
$privacy = $this->getPost('privacy');
|
||||
|
||||
// Валидация
|
||||
if (empty($fullName) || strlen($fullName) < 3) {
|
||||
$errors[] = 'ФИО должно содержать минимум 3 символа';
|
||||
}
|
||||
@@ -136,7 +118,6 @@ class AuthController extends Controller
|
||||
$errors[] = 'Необходимо согласие с условиями обработки персональных данных';
|
||||
}
|
||||
|
||||
// Проверяем существование email
|
||||
if (empty($errors) && $this->userModel->emailExists($email)) {
|
||||
$errors[] = 'Пользователь с таким email уже существует';
|
||||
}
|
||||
@@ -153,7 +134,6 @@ class AuthController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Создаем пользователя
|
||||
try {
|
||||
$userId = $this->userModel->register([
|
||||
'email' => $email,
|
||||
@@ -167,10 +147,7 @@ class AuthController extends Controller
|
||||
throw new \Exception('Ошибка при создании пользователя');
|
||||
}
|
||||
|
||||
// Получаем созданного пользователя
|
||||
$user = $this->userModel->find($userId);
|
||||
|
||||
// Устанавливаем сессию
|
||||
$this->setSession($user);
|
||||
|
||||
$_SESSION['registration_success'] = 'Регистрация прошла успешно!';
|
||||
@@ -188,9 +165,6 @@ class AuthController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Выход из системы
|
||||
*/
|
||||
public function logout(): void
|
||||
{
|
||||
session_destroy();
|
||||
@@ -199,9 +173,6 @@ class AuthController extends Controller
|
||||
$this->redirect('/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить сессию пользователя
|
||||
*/
|
||||
private function setSession(array $user): void
|
||||
{
|
||||
$_SESSION['user_id'] = $user['user_id'];
|
||||
@@ -214,4 +185,3 @@ class AuthController extends Controller
|
||||
$_SESSION['login_time'] = time();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ use App\Core\Controller;
|
||||
use App\Models\Cart;
|
||||
use App\Models\Product;
|
||||
|
||||
/**
|
||||
* CartController - контроллер корзины
|
||||
*/
|
||||
class CartController extends Controller
|
||||
{
|
||||
private Cart $cartModel;
|
||||
@@ -20,9 +17,6 @@ class CartController extends Controller
|
||||
$this->productModel = new Product();
|
||||
}
|
||||
|
||||
/**
|
||||
* Страница корзины
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
@@ -39,9 +33,6 @@ class CartController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить товар в корзину
|
||||
*/
|
||||
public function add(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -64,7 +55,6 @@ class CartController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем наличие товара
|
||||
$product = $this->productModel->find($productId);
|
||||
|
||||
if (!$product || !$product['is_available']) {
|
||||
@@ -75,7 +65,6 @@ class CartController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем количество на складе
|
||||
$cartItem = $this->cartModel->getItem($userId, $productId);
|
||||
$currentQty = $cartItem ? $cartItem['quantity'] : 0;
|
||||
$newQty = $currentQty + $quantity;
|
||||
@@ -88,7 +77,6 @@ class CartController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Добавляем в корзину
|
||||
$result = $this->cartModel->addItem($userId, $productId, $quantity);
|
||||
|
||||
if ($result) {
|
||||
@@ -106,9 +94,6 @@ class CartController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить количество товара
|
||||
*/
|
||||
public function update(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -124,7 +109,6 @@ class CartController extends Controller
|
||||
$userId = $this->getCurrentUser()['id'];
|
||||
|
||||
if ($quantity <= 0) {
|
||||
// Если количество 0 или меньше - удаляем
|
||||
$this->cartModel->removeItem($userId, $productId);
|
||||
$cartCount = $this->cartModel->getCount($userId);
|
||||
$this->json([
|
||||
@@ -134,7 +118,6 @@ class CartController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Проверяем наличие на складе
|
||||
$product = $this->productModel->find($productId);
|
||||
if (!$product || $quantity > $product['stock_quantity']) {
|
||||
$this->json([
|
||||
@@ -161,9 +144,6 @@ class CartController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить товар из корзины
|
||||
*/
|
||||
public function remove(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -193,9 +173,6 @@ class CartController extends Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество товаров в корзине
|
||||
*/
|
||||
public function count(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -215,4 +192,3 @@ class CartController extends Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,8 @@ namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
|
||||
/**
|
||||
* HomeController - контроллер главной страницы
|
||||
*/
|
||||
class HomeController extends Controller
|
||||
{
|
||||
/**
|
||||
* Главная страница
|
||||
*/
|
||||
public function index(): void
|
||||
{
|
||||
$user = $this->getCurrentUser();
|
||||
@@ -23,4 +17,3 @@ class HomeController extends Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ use App\Core\Controller;
|
||||
use App\Models\Order;
|
||||
use App\Models\Cart;
|
||||
|
||||
/**
|
||||
* OrderController - контроллер заказов
|
||||
*/
|
||||
class OrderController extends Controller
|
||||
{
|
||||
private Order $orderModel;
|
||||
@@ -20,9 +17,6 @@ class OrderController extends Controller
|
||||
$this->cartModel = new Cart();
|
||||
}
|
||||
|
||||
/**
|
||||
* Страница оформления заказа (корзина)
|
||||
*/
|
||||
public function checkout(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
@@ -39,9 +33,6 @@ class OrderController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создание заказа
|
||||
*/
|
||||
public function create(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -63,7 +54,6 @@ class OrderController extends Controller
|
||||
return;
|
||||
}
|
||||
|
||||
// Получаем данные заказа
|
||||
$orderData = [
|
||||
'customer_name' => $this->getPost('full_name', $user['full_name']),
|
||||
'customer_email' => $this->getPost('email', $user['email']),
|
||||
@@ -79,7 +69,6 @@ class OrderController extends Controller
|
||||
'notes' => $this->getPost('notes', '')
|
||||
];
|
||||
|
||||
// Валидация
|
||||
if (empty($orderData['customer_name'])) {
|
||||
$this->json([
|
||||
'success' => false,
|
||||
@@ -122,4 +111,3 @@ class OrderController extends Controller
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,14 +4,8 @@ namespace App\Controllers;
|
||||
|
||||
use App\Core\Controller;
|
||||
|
||||
/**
|
||||
* PageController - контроллер статических страниц
|
||||
*/
|
||||
class PageController extends Controller
|
||||
{
|
||||
/**
|
||||
* Страница услуг
|
||||
*/
|
||||
public function services(): void
|
||||
{
|
||||
$this->view('pages/services', [
|
||||
@@ -20,9 +14,6 @@ class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Страница доставки и оплаты
|
||||
*/
|
||||
public function delivery(): void
|
||||
{
|
||||
$this->view('pages/delivery', [
|
||||
@@ -31,9 +22,6 @@ class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Страница гарантии
|
||||
*/
|
||||
public function warranty(): void
|
||||
{
|
||||
$this->view('pages/warranty', [
|
||||
@@ -42,4 +30,3 @@ class PageController extends Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -6,9 +6,6 @@ use App\Core\Controller;
|
||||
use App\Models\Product;
|
||||
use App\Models\Category;
|
||||
|
||||
/**
|
||||
* ProductController - контроллер товаров и каталога
|
||||
*/
|
||||
class ProductController extends Controller
|
||||
{
|
||||
private Product $productModel;
|
||||
@@ -20,9 +17,6 @@ class ProductController extends Controller
|
||||
$this->categoryModel = new Category();
|
||||
}
|
||||
|
||||
/**
|
||||
* Каталог товаров
|
||||
*/
|
||||
public function catalog(): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
@@ -30,7 +24,6 @@ class ProductController extends Controller
|
||||
$user = $this->getCurrentUser();
|
||||
$isAdmin = $this->isAdmin();
|
||||
|
||||
// Получаем параметры фильтрации
|
||||
$filters = [
|
||||
'category_id' => (int) $this->getQuery('category', 0),
|
||||
'search' => $this->getQuery('search', ''),
|
||||
@@ -42,7 +35,6 @@ class ProductController extends Controller
|
||||
|
||||
$showAll = $isAdmin && $this->getQuery('show_all') === '1';
|
||||
|
||||
// Получаем данные
|
||||
$categories = $this->categoryModel->getActive();
|
||||
$products = $showAll
|
||||
? $this->productModel->getAllForAdmin(true)
|
||||
@@ -51,7 +43,6 @@ class ProductController extends Controller
|
||||
$availableColors = $this->productModel->getAvailableColors();
|
||||
$availableMaterials = $this->productModel->getAvailableMaterials();
|
||||
|
||||
// Подкатегории для выбранной категории
|
||||
$subcategories = [];
|
||||
if ($filters['category_id'] > 0) {
|
||||
$subcategories = $this->categoryModel->getChildren($filters['category_id']);
|
||||
@@ -72,9 +63,6 @@ class ProductController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Страница товара
|
||||
*/
|
||||
public function show(int $id): void
|
||||
{
|
||||
$this->requireAuth();
|
||||
@@ -99,4 +87,3 @@ class ProductController extends Controller
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,66 +2,68 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* App - главный класс приложения
|
||||
*/
|
||||
class App
|
||||
{
|
||||
private Router $router;
|
||||
private static ?App $instance = null;
|
||||
private array $config = [];
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
self::$instance = $this;
|
||||
|
||||
// Регистрируем автозагрузчик сразу
|
||||
$this->registerAutoloader();
|
||||
|
||||
$this->router = new Router();
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить экземпляр приложения
|
||||
*/
|
||||
public static function getInstance(): ?self
|
||||
{
|
||||
return self::$instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить роутер
|
||||
*/
|
||||
public function getRouter(): Router
|
||||
{
|
||||
return $this->router;
|
||||
}
|
||||
|
||||
/**
|
||||
* Инициализация приложения
|
||||
*/
|
||||
public function getConfig(string $key = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
return $this->config;
|
||||
}
|
||||
return $this->config[$key] ?? null;
|
||||
}
|
||||
|
||||
public function init(): self
|
||||
{
|
||||
// Запускаем сессию
|
||||
$this->loadConfig();
|
||||
date_default_timezone_set($this->config['timezone'] ?? 'Europe/Moscow');
|
||||
|
||||
if (session_status() === PHP_SESSION_NONE) {
|
||||
session_start();
|
||||
}
|
||||
|
||||
// Настраиваем обработку ошибок
|
||||
$this->setupErrorHandling();
|
||||
|
||||
// Загружаем маршруты
|
||||
$this->loadRoutes();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Регистрация автозагрузчика классов
|
||||
*/
|
||||
private function loadConfig(): void
|
||||
{
|
||||
$configPath = $this->getBasePath() . '/config/app.php';
|
||||
if (file_exists($configPath)) {
|
||||
$this->config = require $configPath;
|
||||
}
|
||||
}
|
||||
|
||||
public function getBasePath(): string
|
||||
{
|
||||
return defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 2);
|
||||
}
|
||||
|
||||
private function registerAutoloader(): void
|
||||
{
|
||||
spl_autoload_register(function ($class) {
|
||||
// Преобразуем namespace в путь к файлу
|
||||
$prefix = 'App\\';
|
||||
$baseDir = dirname(__DIR__) . '/';
|
||||
|
||||
@@ -79,14 +81,9 @@ class App
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Настройка обработки ошибок
|
||||
*/
|
||||
private function setupErrorHandling(): void
|
||||
{
|
||||
$config = require dirname(__DIR__, 2) . '/config/app.php';
|
||||
|
||||
if ($config['debug'] ?? false) {
|
||||
if ($this->config['debug'] ?? false) {
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', '1');
|
||||
} else {
|
||||
@@ -103,16 +100,11 @@ class App
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Обработка исключений
|
||||
*/
|
||||
private function handleException(\Throwable $e): void
|
||||
{
|
||||
$config = require dirname(__DIR__, 2) . '/config/app.php';
|
||||
|
||||
http_response_code(500);
|
||||
|
||||
if ($config['debug'] ?? false) {
|
||||
if ($this->config['debug'] ?? false) {
|
||||
echo "<h1>Ошибка приложения</h1>";
|
||||
echo "<p><strong>Сообщение:</strong> " . htmlspecialchars($e->getMessage()) . "</p>";
|
||||
echo "<p><strong>Файл:</strong> " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "</p>";
|
||||
@@ -122,12 +114,9 @@ class App
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Загрузка маршрутов
|
||||
*/
|
||||
private function loadRoutes(): void
|
||||
{
|
||||
$routesFile = dirname(__DIR__, 2) . '/config/routes.php';
|
||||
$routesFile = $this->getBasePath() . '/config/routes.php';
|
||||
|
||||
if (file_exists($routesFile)) {
|
||||
$router = $this->router;
|
||||
@@ -135,21 +124,17 @@ class App
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Запуск приложения
|
||||
*/
|
||||
public function run(): void
|
||||
{
|
||||
$uri = $_SERVER['REQUEST_URI'] ?? '/';
|
||||
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
|
||||
|
||||
// Удаляем базовый путь, если он есть
|
||||
$basePath = '/cite_practica';
|
||||
if (strpos($uri, $basePath) === 0) {
|
||||
$basePath = $this->config['base_path'] ?? '';
|
||||
|
||||
if (!empty($basePath) && strpos($uri, $basePath) === 0) {
|
||||
$uri = substr($uri, strlen($basePath));
|
||||
}
|
||||
|
||||
// Если URI пустой, делаем его корневым
|
||||
if (empty($uri) || $uri === false) {
|
||||
$uri = '/';
|
||||
}
|
||||
@@ -161,4 +146,3 @@ class App
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,35 +2,20 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* Controller - базовый класс контроллера
|
||||
*/
|
||||
abstract class Controller
|
||||
{
|
||||
/**
|
||||
* Данные для передачи в представление
|
||||
*/
|
||||
protected array $data = [];
|
||||
|
||||
/**
|
||||
* Отрендерить представление
|
||||
*/
|
||||
protected function view(string $view, array $data = [], string $layout = 'main'): void
|
||||
{
|
||||
echo View::render($view, $data, $layout);
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрендерить представление без layout
|
||||
*/
|
||||
protected function viewPartial(string $view, array $data = []): void
|
||||
{
|
||||
echo View::render($view, $data, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Вернуть JSON ответ
|
||||
*/
|
||||
protected function json(array $data, int $statusCode = 200): void
|
||||
{
|
||||
http_response_code($statusCode);
|
||||
@@ -39,18 +24,12 @@ abstract class Controller
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Редирект на другой URL
|
||||
*/
|
||||
protected function redirect(string $url): void
|
||||
{
|
||||
header("Location: {$url}");
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить текущего пользователя из сессии
|
||||
*/
|
||||
protected function getCurrentUser(): ?array
|
||||
{
|
||||
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
|
||||
@@ -68,25 +47,16 @@ abstract class Controller
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, авторизован ли пользователь
|
||||
*/
|
||||
protected function isAuthenticated(): bool
|
||||
{
|
||||
return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли пользователь администратором
|
||||
*/
|
||||
protected function isAdmin(): bool
|
||||
{
|
||||
return $this->isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Требовать авторизацию
|
||||
*/
|
||||
protected function requireAuth(): void
|
||||
{
|
||||
if (!$this->isAuthenticated()) {
|
||||
@@ -95,9 +65,6 @@ abstract class Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Требовать права администратора
|
||||
*/
|
||||
protected function requireAdmin(): void
|
||||
{
|
||||
if (!$this->isAdmin()) {
|
||||
@@ -105,9 +72,6 @@ abstract class Controller
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить POST данные
|
||||
*/
|
||||
protected function getPost(?string $key = null, $default = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
@@ -116,9 +80,6 @@ abstract class Controller
|
||||
return $_POST[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить GET данные
|
||||
*/
|
||||
protected function getQuery(?string $key = null, $default = null)
|
||||
{
|
||||
if ($key === null) {
|
||||
@@ -127,17 +88,11 @@ abstract class Controller
|
||||
return $_GET[$key] ?? $default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Установить flash-сообщение
|
||||
*/
|
||||
protected function setFlash(string $type, string $message): void
|
||||
{
|
||||
$_SESSION['flash'][$type] = $message;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить flash-сообщение
|
||||
*/
|
||||
protected function getFlash(string $type): ?string
|
||||
{
|
||||
$message = $_SESSION['flash'][$type] ?? null;
|
||||
@@ -145,4 +100,3 @@ abstract class Controller
|
||||
return $message;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* Database - Singleton класс для подключения к PostgreSQL
|
||||
*/
|
||||
class Database
|
||||
{
|
||||
private static ?Database $instance = null;
|
||||
@@ -38,9 +35,6 @@ class Database
|
||||
return $this->connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SELECT запрос
|
||||
*/
|
||||
public function query(string $sql, array $params = []): array
|
||||
{
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
@@ -48,9 +42,6 @@ class Database
|
||||
return $stmt->fetchAll();
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить SELECT запрос и получить одну запись
|
||||
*/
|
||||
public function queryOne(string $sql, array $params = []): ?array
|
||||
{
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
@@ -59,52 +50,36 @@ class Database
|
||||
return $result ?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить INSERT/UPDATE/DELETE запрос
|
||||
*/
|
||||
public function execute(string $sql, array $params = []): bool
|
||||
{
|
||||
$stmt = $this->connection->prepare($sql);
|
||||
return $stmt->execute($params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить ID последней вставленной записи
|
||||
*/
|
||||
public function lastInsertId(): string
|
||||
{
|
||||
return $this->connection->lastInsertId();
|
||||
}
|
||||
|
||||
/**
|
||||
* Начать транзакцию
|
||||
*/
|
||||
public function beginTransaction(): bool
|
||||
{
|
||||
return $this->connection->beginTransaction();
|
||||
}
|
||||
|
||||
/**
|
||||
* Подтвердить транзакцию
|
||||
*/
|
||||
public function commit(): bool
|
||||
{
|
||||
return $this->connection->commit();
|
||||
}
|
||||
|
||||
/**
|
||||
* Откатить транзакцию
|
||||
*/
|
||||
public function rollBack(): bool
|
||||
{
|
||||
return $this->connection->rollBack();
|
||||
}
|
||||
|
||||
// Запрещаем клонирование и десериализацию
|
||||
private function __clone() {}
|
||||
|
||||
public function __wakeup()
|
||||
{
|
||||
throw new \Exception("Десериализация Singleton запрещена");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* Model - базовый класс модели
|
||||
*/
|
||||
abstract class Model
|
||||
{
|
||||
protected Database $db;
|
||||
@@ -16,18 +13,12 @@ abstract class Model
|
||||
$this->db = Database::getInstance();
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти запись по первичному ключу
|
||||
*/
|
||||
public function find(int $id): ?array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?";
|
||||
return $this->db->queryOne($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все записи
|
||||
*/
|
||||
public function all(?string $orderBy = null): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}";
|
||||
@@ -37,9 +28,6 @@ abstract class Model
|
||||
return $this->db->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти записи по условию
|
||||
*/
|
||||
public function where(array $conditions, ?string $orderBy = null): array
|
||||
{
|
||||
$where = [];
|
||||
@@ -59,9 +47,6 @@ abstract class Model
|
||||
return $this->db->query($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти одну запись по условию
|
||||
*/
|
||||
public function findWhere(array $conditions): ?array
|
||||
{
|
||||
$where = [];
|
||||
@@ -76,9 +61,6 @@ abstract class Model
|
||||
return $this->db->queryOne($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать новую запись
|
||||
*/
|
||||
public function create(array $data): ?int
|
||||
{
|
||||
$columns = array_keys($data);
|
||||
@@ -98,9 +80,6 @@ abstract class Model
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить запись
|
||||
*/
|
||||
public function update(int $id, array $data): bool
|
||||
{
|
||||
$set = [];
|
||||
@@ -122,18 +101,12 @@ abstract class Model
|
||||
return $this->db->execute($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить запись
|
||||
*/
|
||||
public function delete(int $id): bool
|
||||
{
|
||||
$sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?";
|
||||
return $this->db->execute($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Подсчитать количество записей
|
||||
*/
|
||||
public function count(array $conditions = []): int
|
||||
{
|
||||
$sql = "SELECT COUNT(*) FROM {$this->table}";
|
||||
@@ -153,28 +126,18 @@ abstract class Model
|
||||
return (int) $stmt->fetchColumn();
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить произвольный SQL запрос
|
||||
*/
|
||||
protected function query(string $sql, array $params = []): array
|
||||
{
|
||||
return $this->db->query($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить произвольный SQL запрос и получить одну запись
|
||||
*/
|
||||
protected function queryOne(string $sql, array $params = []): ?array
|
||||
{
|
||||
return $this->db->queryOne($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Выполнить произвольный SQL запрос (INSERT/UPDATE/DELETE)
|
||||
*/
|
||||
protected function execute(string $sql, array $params = []): bool
|
||||
{
|
||||
return $this->db->execute($sql, $params);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,11 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* Router - маршрутизатор запросов
|
||||
*/
|
||||
class Router
|
||||
{
|
||||
private array $routes = [];
|
||||
private array $params = [];
|
||||
|
||||
/**
|
||||
* Добавить маршрут
|
||||
*/
|
||||
public function add(string $method, string $route, string $controller, string $action): self
|
||||
{
|
||||
$this->routes[] = [
|
||||
@@ -24,25 +18,16 @@ class Router
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET маршрут
|
||||
*/
|
||||
public function get(string $route, string $controller, string $action): self
|
||||
{
|
||||
return $this->add('GET', $route, $controller, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST маршрут
|
||||
*/
|
||||
public function post(string $route, string $controller, string $action): self
|
||||
{
|
||||
return $this->add('POST', $route, $controller, $action);
|
||||
}
|
||||
|
||||
/**
|
||||
* Найти маршрут по URL и методу
|
||||
*/
|
||||
public function match(string $url, string $method): ?array
|
||||
{
|
||||
$method = strtoupper($method);
|
||||
@@ -57,7 +42,6 @@ class Router
|
||||
$pattern = $this->convertRouteToRegex($route['route']);
|
||||
|
||||
if (preg_match($pattern, $url, $matches)) {
|
||||
// Извлекаем параметры из URL
|
||||
$this->params = $this->extractParams($route['route'], $matches);
|
||||
|
||||
return [
|
||||
@@ -71,27 +55,16 @@ class Router
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразовать маршрут в регулярное выражение
|
||||
*/
|
||||
private function convertRouteToRegex(string $route): string
|
||||
{
|
||||
$route = trim($route, '/');
|
||||
|
||||
// Заменяем {param} на regex группу
|
||||
$pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '([^/]+)', $route);
|
||||
|
||||
return '#^' . $pattern . '$#';
|
||||
}
|
||||
|
||||
/**
|
||||
* Извлечь параметры из совпадений
|
||||
*/
|
||||
private function extractParams(string $route, array $matches): array
|
||||
{
|
||||
$params = [];
|
||||
|
||||
// Находим все {param} в маршруте
|
||||
preg_match_all('/\{([a-zA-Z_]+)\}/', $route, $paramNames);
|
||||
|
||||
foreach ($paramNames[1] as $index => $name) {
|
||||
@@ -103,9 +76,6 @@ class Router
|
||||
return $params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить query string из URL
|
||||
*/
|
||||
private function removeQueryString(string $url): string
|
||||
{
|
||||
if ($pos = strpos($url, '?')) {
|
||||
@@ -114,17 +84,11 @@ class Router
|
||||
return $url;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить параметры маршрута
|
||||
*/
|
||||
public function getParams(): array
|
||||
{
|
||||
return $this->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* Диспетчеризация запроса
|
||||
*/
|
||||
public function dispatch(string $url, string $method): void
|
||||
{
|
||||
$match = $this->match($url, $method);
|
||||
@@ -148,8 +112,6 @@ class Router
|
||||
throw new \Exception("Метод {$action} не найден в контроллере {$controllerClass}");
|
||||
}
|
||||
|
||||
// Вызываем метод контроллера с параметрами
|
||||
call_user_func_array([$controller, $action], $match['params']);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,24 +2,15 @@
|
||||
|
||||
namespace App\Core;
|
||||
|
||||
/**
|
||||
* View - класс для рендеринга представлений
|
||||
*/
|
||||
class View
|
||||
{
|
||||
private static string $viewsPath = '';
|
||||
|
||||
/**
|
||||
* Установить путь к директории представлений
|
||||
*/
|
||||
public static function setViewsPath(string $path): void
|
||||
{
|
||||
self::$viewsPath = rtrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить путь к директории представлений
|
||||
*/
|
||||
public static function getViewsPath(): string
|
||||
{
|
||||
if (empty(self::$viewsPath)) {
|
||||
@@ -28,9 +19,6 @@ class View
|
||||
return self::$viewsPath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрендерить представление
|
||||
*/
|
||||
public static function render(string $view, array $data = [], ?string $layout = 'main'): string
|
||||
{
|
||||
$viewPath = self::getViewsPath() . '/' . str_replace('.', '/', $view) . '.php';
|
||||
@@ -39,15 +27,12 @@ class View
|
||||
throw new \Exception("Представление не найдено: {$viewPath}");
|
||||
}
|
||||
|
||||
// Извлекаем данные в переменные
|
||||
extract($data);
|
||||
|
||||
// Буферизируем вывод контента
|
||||
ob_start();
|
||||
require $viewPath;
|
||||
$content = ob_get_clean();
|
||||
|
||||
// Если есть layout, оборачиваем контент
|
||||
if ($layout !== null) {
|
||||
$layoutPath = self::getViewsPath() . '/layouts/' . $layout . '.php';
|
||||
|
||||
@@ -63,9 +48,6 @@ class View
|
||||
return $content;
|
||||
}
|
||||
|
||||
/**
|
||||
* Отрендерить partial (часть шаблона)
|
||||
*/
|
||||
public static function partial(string $partial, array $data = []): string
|
||||
{
|
||||
$partialPath = self::getViewsPath() . '/partials/' . $partial . '.php';
|
||||
@@ -81,49 +63,31 @@ class View
|
||||
return ob_get_clean();
|
||||
}
|
||||
|
||||
/**
|
||||
* Экранирование HTML
|
||||
*/
|
||||
public static function escape(string $value): string
|
||||
{
|
||||
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Сокращенный алиас для escape
|
||||
*/
|
||||
public static function e(string $value): string
|
||||
{
|
||||
return self::escape($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование цены
|
||||
*/
|
||||
public static function formatPrice($price): string
|
||||
{
|
||||
return number_format((float)$price, 0, '', ' ') . ' ₽';
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование даты
|
||||
*/
|
||||
public static function formatDate(string $date, string $format = 'd.m.Y'): string
|
||||
{
|
||||
return date($format, strtotime($date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Форматирование даты и времени
|
||||
*/
|
||||
public static function formatDateTime(string $date, string $format = 'd.m.Y H:i'): string
|
||||
{
|
||||
return date($format, strtotime($date));
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить flash-сообщения
|
||||
*/
|
||||
public static function getFlashMessages(): array
|
||||
{
|
||||
$messages = $_SESSION['flash'] ?? [];
|
||||
@@ -131,25 +95,16 @@ class View
|
||||
return $messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, авторизован ли пользователь
|
||||
*/
|
||||
public static function isAuthenticated(): bool
|
||||
{
|
||||
return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, является ли пользователь администратором
|
||||
*/
|
||||
public static function isAdmin(): bool
|
||||
{
|
||||
return self::isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить данные текущего пользователя
|
||||
*/
|
||||
public static function currentUser(): ?array
|
||||
{
|
||||
if (!self::isAuthenticated()) {
|
||||
@@ -165,20 +120,13 @@ class View
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация URL
|
||||
*/
|
||||
public static function url(string $path): string
|
||||
{
|
||||
return '/' . ltrim($path, '/');
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация URL для ассетов
|
||||
*/
|
||||
public static function asset(string $path): string
|
||||
{
|
||||
return '/assets/' . ltrim($path, '/');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,11 @@ namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
/**
|
||||
* Cart - модель корзины
|
||||
*/
|
||||
class Cart extends Model
|
||||
{
|
||||
protected string $table = 'cart';
|
||||
protected string $primaryKey = 'cart_id';
|
||||
|
||||
/**
|
||||
* Получить корзину пользователя
|
||||
*/
|
||||
public function getUserCart(int $userId): array
|
||||
{
|
||||
$sql = "SELECT
|
||||
@@ -33,9 +27,6 @@ class Cart extends Model
|
||||
return $this->query($sql, [$userId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить общую сумму корзины
|
||||
*/
|
||||
public function getCartTotal(int $userId): array
|
||||
{
|
||||
$sql = "SELECT
|
||||
@@ -52,9 +43,6 @@ class Cart extends Model
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить количество товаров в корзине
|
||||
*/
|
||||
public function getCount(int $userId): int
|
||||
{
|
||||
$sql = "SELECT COALESCE(SUM(quantity), 0) as total FROM {$this->table} WHERE user_id = ?";
|
||||
@@ -62,19 +50,14 @@ class Cart extends Model
|
||||
return (int) $result['total'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить товар в корзину
|
||||
*/
|
||||
public function addItem(int $userId, int $productId, int $quantity = 1): bool
|
||||
{
|
||||
// Проверяем, есть ли уже этот товар в корзине
|
||||
$existing = $this->findWhere([
|
||||
'user_id' => $userId,
|
||||
'product_id' => $productId
|
||||
]);
|
||||
|
||||
if ($existing) {
|
||||
// Увеличиваем количество
|
||||
$newQuantity = $existing['quantity'] + $quantity;
|
||||
return $this->update($existing['cart_id'], [
|
||||
'quantity' => $newQuantity,
|
||||
@@ -82,7 +65,6 @@ class Cart extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
// Добавляем новую запись
|
||||
return $this->create([
|
||||
'user_id' => $userId,
|
||||
'product_id' => $productId,
|
||||
@@ -90,9 +72,6 @@ class Cart extends Model
|
||||
]) !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить количество товара в корзине
|
||||
*/
|
||||
public function updateQuantity(int $userId, int $productId, int $quantity): bool
|
||||
{
|
||||
$sql = "UPDATE {$this->table}
|
||||
@@ -101,27 +80,18 @@ class Cart extends Model
|
||||
return $this->execute($sql, [$quantity, $userId, $productId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Удалить товар из корзины
|
||||
*/
|
||||
public function removeItem(int $userId, int $productId): bool
|
||||
{
|
||||
$sql = "DELETE FROM {$this->table} WHERE user_id = ? AND product_id = ?";
|
||||
return $this->execute($sql, [$userId, $productId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Очистить корзину пользователя
|
||||
*/
|
||||
public function clearCart(int $userId): bool
|
||||
{
|
||||
$sql = "DELETE FROM {$this->table} WHERE user_id = ?";
|
||||
return $this->execute($sql, [$userId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить, есть ли товар в корзине
|
||||
*/
|
||||
public function hasItem(int $userId, int $productId): bool
|
||||
{
|
||||
$item = $this->findWhere([
|
||||
@@ -131,9 +101,6 @@ class Cart extends Model
|
||||
return $item !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить товар из корзины
|
||||
*/
|
||||
public function getItem(int $userId, int $productId): ?array
|
||||
{
|
||||
return $this->findWhere([
|
||||
@@ -142,4 +109,3 @@ class Cart extends Model
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,11 @@ namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
/**
|
||||
* Category - модель категории товаров
|
||||
*/
|
||||
class Category extends Model
|
||||
{
|
||||
protected string $table = 'categories';
|
||||
protected string $primaryKey = 'category_id';
|
||||
|
||||
/**
|
||||
* Получить активные категории
|
||||
*/
|
||||
public function getActive(): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -23,9 +17,6 @@ class Category extends Model
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить родительские категории (без parent_id)
|
||||
*/
|
||||
public function getParent(): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -34,9 +25,6 @@ class Category extends Model
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить подкатегории
|
||||
*/
|
||||
public function getChildren(int $parentId): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -45,17 +33,11 @@ class Category extends Model
|
||||
return $this->query($sql, [$parentId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить категорию по slug
|
||||
*/
|
||||
public function findBySlug(string $slug): ?array
|
||||
{
|
||||
return $this->findWhere(['slug' => $slug]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все категории с количеством товаров
|
||||
*/
|
||||
public function getAllWithProductCount(): array
|
||||
{
|
||||
$sql = "SELECT c1.*, c2.name as parent_name,
|
||||
@@ -66,9 +48,6 @@ class Category extends Model
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать категорию
|
||||
*/
|
||||
public function createCategory(array $data): ?int
|
||||
{
|
||||
$slug = $this->generateSlug($data['name']);
|
||||
@@ -83,9 +62,6 @@ class Category extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить категорию
|
||||
*/
|
||||
public function updateCategory(int $id, array $data): bool
|
||||
{
|
||||
$updateData = [
|
||||
@@ -101,23 +77,17 @@ class Category extends Model
|
||||
return $this->update($id, $updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Безопасное удаление категории
|
||||
*/
|
||||
public function safeDelete(int $id): array
|
||||
{
|
||||
// Проверяем наличие товаров
|
||||
$sql = "SELECT COUNT(*) as cnt FROM products WHERE category_id = ?";
|
||||
$result = $this->queryOne($sql, [$id]);
|
||||
$productCount = (int) $result['cnt'];
|
||||
|
||||
// Проверяем наличие дочерних категорий
|
||||
$sql = "SELECT COUNT(*) as cnt FROM {$this->table} WHERE parent_id = ?";
|
||||
$result = $this->queryOne($sql, [$id]);
|
||||
$childCount = (int) $result['cnt'];
|
||||
|
||||
if ($productCount > 0 || $childCount > 0) {
|
||||
// Скрываем вместо удаления
|
||||
$this->update($id, ['is_active' => false]);
|
||||
return [
|
||||
'deleted' => false,
|
||||
@@ -126,19 +96,14 @@ class Category extends Model
|
||||
];
|
||||
}
|
||||
|
||||
// Удаляем полностью
|
||||
$this->delete($id);
|
||||
return ['deleted' => true, 'hidden' => false];
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация slug из названия
|
||||
*/
|
||||
private function generateSlug(string $name): string
|
||||
{
|
||||
$slug = mb_strtolower($name);
|
||||
|
||||
// Транслитерация
|
||||
$transliteration = [
|
||||
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
|
||||
'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
|
||||
@@ -156,4 +121,3 @@ class Category extends Model
|
||||
return $slug;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -5,17 +5,11 @@ namespace App\Models;
|
||||
use App\Core\Model;
|
||||
use App\Core\Database;
|
||||
|
||||
/**
|
||||
* Order - модель заказа
|
||||
*/
|
||||
class Order extends Model
|
||||
{
|
||||
protected string $table = 'orders';
|
||||
protected string $primaryKey = 'order_id';
|
||||
|
||||
/**
|
||||
* Создать заказ из корзины
|
||||
*/
|
||||
public function createFromCart(int $userId, array $cartItems, array $orderData): ?array
|
||||
{
|
||||
$db = Database::getInstance();
|
||||
@@ -23,7 +17,6 @@ class Order extends Model
|
||||
try {
|
||||
$db->beginTransaction();
|
||||
|
||||
// Считаем итоговую сумму
|
||||
$subtotal = 0;
|
||||
foreach ($cartItems as $item) {
|
||||
$subtotal += $item['price'] * $item['quantity'];
|
||||
@@ -33,10 +26,8 @@ class Order extends Model
|
||||
$deliveryPrice = (float) ($orderData['delivery_price'] ?? 2000);
|
||||
$finalAmount = $subtotal - $discountAmount + $deliveryPrice;
|
||||
|
||||
// Генерируем номер заказа
|
||||
$orderNumber = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999);
|
||||
|
||||
// Создаем заказ
|
||||
$orderId = $this->create([
|
||||
'user_id' => $userId,
|
||||
'order_number' => $orderNumber,
|
||||
@@ -61,18 +52,15 @@ class Order extends Model
|
||||
throw new \Exception('Не удалось создать заказ');
|
||||
}
|
||||
|
||||
// Добавляем товары в заказ
|
||||
foreach ($cartItems as $item) {
|
||||
$this->addOrderItem($orderId, $item);
|
||||
}
|
||||
|
||||
// Уменьшаем количество товаров на складе
|
||||
$productModel = new Product();
|
||||
foreach ($cartItems as $item) {
|
||||
$productModel->decreaseStock($item['product_id'], $item['quantity']);
|
||||
}
|
||||
|
||||
// Очищаем корзину
|
||||
$cartModel = new Cart();
|
||||
$cartModel->clearCart($userId);
|
||||
|
||||
@@ -90,9 +78,6 @@ class Order extends Model
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Добавить товар в заказ
|
||||
*/
|
||||
private function addOrderItem(int $orderId, array $item): void
|
||||
{
|
||||
$sql = "INSERT INTO order_items
|
||||
@@ -111,9 +96,6 @@ class Order extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить заказ с подробностями
|
||||
*/
|
||||
public function getWithDetails(int $orderId): ?array
|
||||
{
|
||||
$sql = "SELECT o.*, u.email as user_email, u.full_name as user_full_name
|
||||
@@ -130,9 +112,6 @@ class Order extends Model
|
||||
return $order;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить товары заказа
|
||||
*/
|
||||
public function getOrderItems(int $orderId): array
|
||||
{
|
||||
$sql = "SELECT oi.*, p.image_url
|
||||
@@ -142,9 +121,6 @@ class Order extends Model
|
||||
return $this->query($sql, [$orderId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить заказы пользователя
|
||||
*/
|
||||
public function getUserOrders(int $userId, int $limit = 50): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -154,9 +130,6 @@ class Order extends Model
|
||||
return $this->query($sql, [$userId, $limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все заказы для админки
|
||||
*/
|
||||
public function getAllForAdmin(int $limit = 50): array
|
||||
{
|
||||
$sql = "SELECT o.*, u.email as user_email
|
||||
@@ -167,9 +140,6 @@ class Order extends Model
|
||||
return $this->query($sql, [$limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить статус заказа
|
||||
*/
|
||||
public function updateStatus(int $orderId, string $status): bool
|
||||
{
|
||||
$updateData = [
|
||||
@@ -184,25 +154,19 @@ class Order extends Model
|
||||
return $this->update($orderId, $updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить статистику заказов
|
||||
*/
|
||||
public function getStats(): array
|
||||
{
|
||||
$stats = [];
|
||||
|
||||
// Общее количество заказов
|
||||
$result = $this->queryOne("SELECT COUNT(*) as cnt FROM {$this->table}");
|
||||
$stats['total'] = (int) $result['cnt'];
|
||||
|
||||
// Выручка
|
||||
$result = $this->queryOne(
|
||||
"SELECT COALESCE(SUM(final_amount), 0) as revenue
|
||||
FROM {$this->table} WHERE status = 'completed'"
|
||||
);
|
||||
$stats['revenue'] = (float) $result['revenue'];
|
||||
|
||||
// По статусам
|
||||
$statuses = $this->query(
|
||||
"SELECT status, COUNT(*) as cnt FROM {$this->table} GROUP BY status"
|
||||
);
|
||||
@@ -214,4 +178,3 @@ class Order extends Model
|
||||
return $stats;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,17 +4,11 @@ namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
/**
|
||||
* Product - модель товара
|
||||
*/
|
||||
class Product extends Model
|
||||
{
|
||||
protected string $table = 'products';
|
||||
protected string $primaryKey = 'product_id';
|
||||
|
||||
/**
|
||||
* Получить товар с категорией
|
||||
*/
|
||||
public function findWithCategory(int $id): ?array
|
||||
{
|
||||
$sql = "SELECT p.*, c.name as category_name, c.slug as category_slug
|
||||
@@ -24,9 +18,6 @@ class Product extends Model
|
||||
return $this->queryOne($sql, [$id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить доступные товары
|
||||
*/
|
||||
public function getAvailable(array $filters = [], int $limit = 50): array
|
||||
{
|
||||
$sql = "SELECT p.*, c.name as category_name
|
||||
@@ -35,13 +26,11 @@ class Product extends Model
|
||||
WHERE p.is_available = TRUE";
|
||||
$params = [];
|
||||
|
||||
// Фильтр по категории
|
||||
if (!empty($filters['category_id'])) {
|
||||
$sql .= " AND p.category_id = ?";
|
||||
$params[] = $filters['category_id'];
|
||||
}
|
||||
|
||||
// Фильтр по цене
|
||||
if (!empty($filters['min_price'])) {
|
||||
$sql .= " AND p.price >= ?";
|
||||
$params[] = $filters['min_price'];
|
||||
@@ -51,21 +40,18 @@ class Product extends Model
|
||||
$params[] = $filters['max_price'];
|
||||
}
|
||||
|
||||
// Фильтр по цветам
|
||||
if (!empty($filters['colors']) && is_array($filters['colors'])) {
|
||||
$placeholders = implode(',', array_fill(0, count($filters['colors']), '?'));
|
||||
$sql .= " AND p.color IN ({$placeholders})";
|
||||
$params = array_merge($params, $filters['colors']);
|
||||
}
|
||||
|
||||
// Фильтр по материалам
|
||||
if (!empty($filters['materials']) && is_array($filters['materials'])) {
|
||||
$placeholders = implode(',', array_fill(0, count($filters['materials']), '?'));
|
||||
$sql .= " AND p.material IN ({$placeholders})";
|
||||
$params = array_merge($params, $filters['materials']);
|
||||
}
|
||||
|
||||
// Поиск по названию/описанию
|
||||
if (!empty($filters['search'])) {
|
||||
$sql .= " AND (p.name ILIKE ? OR p.description ILIKE ?)";
|
||||
$search = '%' . $filters['search'] . '%';
|
||||
@@ -79,9 +65,6 @@ class Product extends Model
|
||||
return $this->query($sql, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить все товары (включая недоступные) для админки
|
||||
*/
|
||||
public function getAllForAdmin(bool $showAll = true): array
|
||||
{
|
||||
$sql = "SELECT p.*, c.name as category_name
|
||||
@@ -97,9 +80,6 @@ class Product extends Model
|
||||
return $this->query($sql);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить похожие товары
|
||||
*/
|
||||
public function getSimilar(int $productId, int $categoryId, int $limit = 3): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -111,9 +91,6 @@ class Product extends Model
|
||||
return $this->query($sql, [$categoryId, $productId, $limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить доступные цвета
|
||||
*/
|
||||
public function getAvailableColors(): array
|
||||
{
|
||||
$sql = "SELECT DISTINCT color FROM {$this->table}
|
||||
@@ -123,9 +100,6 @@ class Product extends Model
|
||||
return array_column($result, 'color');
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить доступные материалы
|
||||
*/
|
||||
public function getAvailableMaterials(): array
|
||||
{
|
||||
$sql = "SELECT DISTINCT material FROM {$this->table}
|
||||
@@ -135,9 +109,6 @@ class Product extends Model
|
||||
return array_column($result, 'material');
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать товар
|
||||
*/
|
||||
public function createProduct(array $data): ?int
|
||||
{
|
||||
$slug = $this->generateSlug($data['name']);
|
||||
@@ -161,9 +132,6 @@ class Product extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить товар
|
||||
*/
|
||||
public function updateProduct(int $id, array $data): bool
|
||||
{
|
||||
$updateData = [
|
||||
@@ -183,9 +151,6 @@ class Product extends Model
|
||||
return $this->update($id, $updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Уменьшить количество товара на складе
|
||||
*/
|
||||
public function decreaseStock(int $productId, int $quantity): bool
|
||||
{
|
||||
$sql = "UPDATE {$this->table}
|
||||
@@ -195,18 +160,12 @@ class Product extends Model
|
||||
return $this->execute($sql, [$quantity, $productId]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить наличие товара
|
||||
*/
|
||||
public function checkStock(int $productId, int $quantity): bool
|
||||
{
|
||||
$product = $this->find($productId);
|
||||
return $product && $product['is_available'] && $product['stock_quantity'] >= $quantity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация slug
|
||||
*/
|
||||
private function generateSlug(string $name): string
|
||||
{
|
||||
$slug = mb_strtolower($name);
|
||||
@@ -227,13 +186,9 @@ class Product extends Model
|
||||
return trim($slug, '-');
|
||||
}
|
||||
|
||||
/**
|
||||
* Генерация артикула
|
||||
*/
|
||||
private function generateSku(string $name): string
|
||||
{
|
||||
$prefix = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $name), 0, 6));
|
||||
return 'PROD-' . $prefix . '-' . rand(100, 999);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -4,46 +4,30 @@ namespace App\Models;
|
||||
|
||||
use App\Core\Model;
|
||||
|
||||
/**
|
||||
* User - модель пользователя
|
||||
*/
|
||||
class User extends Model
|
||||
{
|
||||
protected string $table = 'users';
|
||||
protected string $primaryKey = 'user_id';
|
||||
|
||||
/**
|
||||
* Найти пользователя по email
|
||||
*/
|
||||
public function findByEmail(string $email): ?array
|
||||
{
|
||||
return $this->findWhere(['email' => $email]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить пароль пользователя
|
||||
*/
|
||||
public function verifyPassword(string $password, string $hash): bool
|
||||
{
|
||||
return password_verify($password, $hash);
|
||||
}
|
||||
|
||||
/**
|
||||
* Хешировать пароль
|
||||
*/
|
||||
public function hashPassword(string $password): string
|
||||
{
|
||||
return password_hash($password, PASSWORD_DEFAULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Создать нового пользователя
|
||||
*/
|
||||
public function register(array $data): ?int
|
||||
{
|
||||
$config = require dirname(__DIR__, 2) . '/config/app.php';
|
||||
|
||||
// Проверяем, является ли email администраторским
|
||||
$isAdmin = in_array(strtolower($data['email']), $config['admin_emails'] ?? []);
|
||||
|
||||
return $this->create([
|
||||
@@ -52,14 +36,11 @@ class User extends Model
|
||||
'full_name' => $data['full_name'],
|
||||
'phone' => $data['phone'] ?? null,
|
||||
'city' => $data['city'] ?? null,
|
||||
'is_admin' => $isAdmin,
|
||||
'is_active' => true
|
||||
'is_admin' => $isAdmin ? 'true' : 'false',
|
||||
'is_active' => 'true'
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Авторизация пользователя
|
||||
*/
|
||||
public function authenticate(string $email, string $password): ?array
|
||||
{
|
||||
$user = $this->findByEmail($email);
|
||||
@@ -76,7 +57,6 @@ class User extends Model
|
||||
return null;
|
||||
}
|
||||
|
||||
// Обновляем время последнего входа
|
||||
$this->update($user['user_id'], [
|
||||
'last_login' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
@@ -84,9 +64,6 @@ class User extends Model
|
||||
return $user;
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить активных пользователей
|
||||
*/
|
||||
public function getActive(int $limit = 50): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -96,9 +73,6 @@ class User extends Model
|
||||
return $this->query($sql, [$limit]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Получить всех пользователей с пагинацией
|
||||
*/
|
||||
public function getAllPaginated(int $limit = 50, int $offset = 0): array
|
||||
{
|
||||
$sql = "SELECT * FROM {$this->table}
|
||||
@@ -107,18 +81,12 @@ class User extends Model
|
||||
return $this->query($sql, [$limit, $offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Проверить существование email
|
||||
*/
|
||||
public function emailExists(string $email): bool
|
||||
{
|
||||
$user = $this->findByEmail($email);
|
||||
return $user !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Обновить профиль пользователя
|
||||
*/
|
||||
public function updateProfile(int $userId, array $data): bool
|
||||
{
|
||||
$allowedFields = ['full_name', 'phone', 'city'];
|
||||
@@ -128,9 +96,6 @@ class User extends Model
|
||||
return $this->update($userId, $updateData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Изменить пароль
|
||||
*/
|
||||
public function changePassword(int $userId, string $newPassword): bool
|
||||
{
|
||||
return $this->update($userId, [
|
||||
@@ -139,9 +104,6 @@ class User extends Model
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Заблокировать/разблокировать пользователя
|
||||
*/
|
||||
public function setActive(int $userId, bool $active): bool
|
||||
{
|
||||
return $this->update($userId, [
|
||||
@@ -150,4 +112,3 @@ class User extends Model
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
<h2><?= $isEdit ? 'Редактирование категории' : 'Добавление категории' ?></h2>
|
||||
|
||||
<a href="/cite_practica/admin/categories" class="btn btn-primary" style="margin-bottom: 20px;">
|
||||
<a href="/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">
|
||||
<form action="/admin/categories/<?= $isEdit ? 'edit/' . $category['category_id'] : 'add' ?>" method="POST">
|
||||
<div class="form-group">
|
||||
<label>Название категории *</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<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">
|
||||
<a href="/admin/categories/add" class="btn btn-success">
|
||||
<i class="fas fa-plus"></i> Добавить категорию
|
||||
</a>
|
||||
</div>
|
||||
@@ -42,10 +42,10 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="/cite_practica/admin/categories/edit/<?= $category['category_id'] ?>" class="btn btn-sm btn-warning" title="Редактировать">
|
||||
<a href="/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;"
|
||||
<form action="/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>
|
||||
|
||||
@@ -28,16 +28,16 @@
|
||||
<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">
|
||||
<a href="/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">
|
||||
<a href="/admin/categories/add" class="btn btn-primary">
|
||||
<i class="fas fa-plus"></i> Добавить категорию
|
||||
</a>
|
||||
<a href="/cite_practica/admin/orders" class="btn btn-primary">
|
||||
<a href="/admin/orders" class="btn btn-primary">
|
||||
<i class="fas fa-shopping-cart"></i> Просмотреть заказы
|
||||
</a>
|
||||
<a href="/cite_practica/catalog" class="btn btn-primary">
|
||||
<a href="/catalog" class="btn btn-primary">
|
||||
<i class="fas fa-store"></i> Перейти в каталог
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php use App\Core\View; ?>
|
||||
|
||||
<a href="/cite_practica/admin/orders" class="btn btn-primary" style="margin-bottom: 20px;">
|
||||
<a href="/admin/orders" class="btn btn-primary" style="margin-bottom: 20px;">
|
||||
<i class="fas fa-arrow-left"></i> Назад к заказам
|
||||
</a>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
|
||||
<div class="form-container">
|
||||
<h3>Статус заказа</h3>
|
||||
<form action="/cite_practica/admin/orders/<?= $order['order_id'] ?>/status" method="POST">
|
||||
<form action="/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>
|
||||
@@ -60,7 +60,7 @@
|
||||
<?php foreach ($order['items'] as $item): ?>
|
||||
<tr>
|
||||
<td>
|
||||
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
|
||||
<img src="/<?= 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>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
<a href="/cite_practica/admin/orders/<?= $order['order_id'] ?>" class="btn btn-sm btn-primary">
|
||||
<a href="/admin/orders/<?= $order['order_id'] ?>" class="btn btn-sm btn-primary">
|
||||
<i class="fas fa-eye"></i> Подробнее
|
||||
</a>
|
||||
</td>
|
||||
|
||||
@@ -2,12 +2,12 @@
|
||||
|
||||
<h2><?= $isEdit ? 'Редактирование товара' : 'Добавление товара' ?></h2>
|
||||
|
||||
<a href="/cite_practica/admin/products" class="btn btn-primary" style="margin-bottom: 20px;">
|
||||
<a href="/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">
|
||||
<form action="/admin/products/<?= $isEdit ? 'edit/' . $product['product_id'] : 'add' ?>" method="POST">
|
||||
<div class="form-group">
|
||||
<label>Название товара *</label>
|
||||
<input type="text" name="name" class="form-control"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
<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">
|
||||
<a href="/admin/products/add" class="btn btn-success">
|
||||
<i class="fas fa-plus"></i> Добавить товар
|
||||
</a>
|
||||
</div>
|
||||
@@ -16,8 +16,8 @@
|
||||
<?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>
|
||||
<a href="/admin/products" class="btn btn-sm <?= !$showAll ? 'btn-primary' : '' ?>">Активные</a>
|
||||
<a href="/admin/products?show_all=1" class="btn btn-sm <?= $showAll ? 'btn-primary' : '' ?>">Все товары</a>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
@@ -38,7 +38,7 @@
|
||||
<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') ?>"
|
||||
<img src="/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
|
||||
style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
|
||||
</td>
|
||||
<td><?= htmlspecialchars($product['name']) ?></td>
|
||||
@@ -54,13 +54,13 @@
|
||||
</td>
|
||||
<td>
|
||||
<div class="action-buttons">
|
||||
<a href="/cite_practica/product/<?= $product['product_id'] ?>" class="btn btn-sm btn-primary" title="Просмотр">
|
||||
<a href="/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="Редактировать">
|
||||
<a href="/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;"
|
||||
<form action="/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>
|
||||
|
||||
@@ -47,7 +47,7 @@
|
||||
|
||||
<div class="auth-actions">
|
||||
<span class="auth-text">Нет аккаунта?</span>
|
||||
<a href="/cite_practica/register" class="login-btn">Зарегистрироваться</a>
|
||||
<a href="/register" class="login-btn">Зарегистрироваться</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@@ -70,13 +70,13 @@ $(document).ready(function() {
|
||||
}
|
||||
|
||||
$.ajax({
|
||||
url: '/cite_practica/login',
|
||||
url: '/login',
|
||||
method: 'POST',
|
||||
data: { email: email, password: password, redirect: redirect },
|
||||
dataType: 'json',
|
||||
success: function(result) {
|
||||
if (result.success) {
|
||||
window.location.href = result.redirect || '/cite_practica/catalog';
|
||||
window.location.href = result.redirect || '/catalog';
|
||||
} else {
|
||||
alert(result.message || 'Ошибка авторизации');
|
||||
}
|
||||
@@ -88,4 +88,3 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -18,10 +18,6 @@
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div style="background: #e8f4fd; padding: 15px; border-radius: 5px; margin: 20px auto; max-width: 800px; text-align: center; font-size: 14px; color: #0c5460;">
|
||||
<i class="fas fa-info-circle"></i> Для доступа к каталогу и оформления заказов необходимо зарегистрироваться
|
||||
</div>
|
||||
|
||||
<div class="profile-container">
|
||||
<div class="profile-left-col">
|
||||
<div class="logo" style="color: white;">AETERNA</div>
|
||||
@@ -39,8 +35,11 @@
|
||||
|
||||
<div class="profile-right-col">
|
||||
<div class="profile-form-block">
|
||||
<div style="margin-bottom: 20px; padding: 12px 15px; background: #e8f4fd; border-radius: 5px; font-size: 13px; color: #0c5460; text-align: center;">
|
||||
<i class="fas fa-info-circle"></i> Для доступа к каталогу и оформления заказов необходимо зарегистрироваться
|
||||
</div>
|
||||
<h2>РЕГИСТРАЦИЯ</h2>
|
||||
<form class="profile-form" action="/cite_practica/register" method="POST" id="registrationForm">
|
||||
<form class="profile-form" action="/register" method="POST" id="registrationForm">
|
||||
<div class="input-group">
|
||||
<label for="fio">ФИО *</label>
|
||||
<input type="text" id="fio" name="fio" placeholder="Введите ваше ФИО"
|
||||
@@ -82,7 +81,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<a href="/cite_practica/login" style="display: block; margin: 15px 0; text-align: center; color: #453227;">
|
||||
<a href="/login" style="display: block; margin: 15px 0; text-align: center; color: #453227;">
|
||||
Уже есть аккаунт? Войти
|
||||
</a>
|
||||
|
||||
@@ -92,4 +91,3 @@
|
||||
</div>
|
||||
</div>
|
||||
</main>
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ use App\Core\View;
|
||||
|
||||
<main class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Корзина</span>
|
||||
<a href="/">Главная</a> • <span class="current-page">Корзина</span>
|
||||
</div>
|
||||
|
||||
<h2 style="color: #453227; margin: 20px 0;">Товары в корзине</h2>
|
||||
@@ -49,7 +49,7 @@ use App\Core\View;
|
||||
<div class="empty-cart">
|
||||
<i class="fas fa-shopping-cart" style="font-size: 48px; color: #ccc; margin-bottom: 20px;"></i>
|
||||
<p>Ваша корзина пуста</p>
|
||||
<a href="/cite_practica/catalog" class="btn primary-btn" style="margin-top: 20px; display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
<a href="/catalog" class="btn primary-btn" style="margin-top: 20px; display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
Продолжить покупки
|
||||
</a>
|
||||
</div>
|
||||
@@ -60,7 +60,17 @@ use App\Core\View;
|
||||
<?php foreach ($cartItems as $item): ?>
|
||||
<div class="products__item" data-product-id="<?= $item['product_id'] ?>" data-price="<?= $item['price'] ?>">
|
||||
<div class="products__image">
|
||||
<img src="/cite_practica/<?= htmlspecialchars($item['image_url'] ?? 'img/1.jpg') ?>"
|
||||
<?php
|
||||
$cartImageUrl = $item['image_url'] ?? '';
|
||||
if (empty($cartImageUrl)) {
|
||||
$cartImageUrl = '/assets/images/1.jpg';
|
||||
} elseif (strpos($cartImageUrl, '/img2/') === 0) {
|
||||
$cartImageUrl = str_replace('/img2/', '/assets/images/', $cartImageUrl);
|
||||
} elseif (strpos($cartImageUrl, 'img2/') === 0) {
|
||||
$cartImageUrl = str_replace('img2/', '/assets/images/', $cartImageUrl);
|
||||
}
|
||||
?>
|
||||
<img src="<?= htmlspecialchars($cartImageUrl) ?>"
|
||||
alt="<?= htmlspecialchars($item['name']) ?>">
|
||||
</div>
|
||||
<div class="products__details">
|
||||
@@ -170,13 +180,12 @@ use App\Core\View;
|
||||
|
||||
<script>
|
||||
$(document).ready(function() {
|
||||
// Обновление количества
|
||||
$('.products__qty-btn').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const productId = $(this).data('id');
|
||||
const isPlus = $(this).hasClass('plus');
|
||||
const $qtyValue = $(this).siblings('.products__qty-value');
|
||||
let quantity = parseInt($qtyValue.text());
|
||||
var productId = $(this).data('id');
|
||||
var isPlus = $(this).hasClass('plus');
|
||||
var $qtyValue = $(this).siblings('.products__qty-value');
|
||||
var quantity = parseInt($qtyValue.text());
|
||||
|
||||
if (isPlus) { quantity++; }
|
||||
else if (quantity > 1) { quantity--; }
|
||||
@@ -185,7 +194,7 @@ $(document).ready(function() {
|
||||
$qtyValue.text(quantity);
|
||||
|
||||
$.ajax({
|
||||
url: '/cite_practica/cart/update',
|
||||
url: '/cart/update',
|
||||
method: 'POST',
|
||||
data: { product_id: productId, quantity: quantity },
|
||||
dataType: 'json',
|
||||
@@ -198,16 +207,15 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
|
||||
// Удаление товара
|
||||
$('.remove-from-cart').on('click', function(e) {
|
||||
e.preventDefault();
|
||||
const productId = $(this).data('id');
|
||||
const $item = $(this).closest('.products__item');
|
||||
var productId = $(this).data('id');
|
||||
var $item = $(this).closest('.products__item');
|
||||
|
||||
if (!confirm('Удалить товар из корзины?')) return;
|
||||
|
||||
$.ajax({
|
||||
url: '/cite_practica/cart/remove',
|
||||
url: '/cart/remove',
|
||||
method: 'POST',
|
||||
data: { product_id: productId },
|
||||
dataType: 'json',
|
||||
@@ -227,31 +235,30 @@ $(document).ready(function() {
|
||||
});
|
||||
|
||||
function updateTotals() {
|
||||
let productsTotal = 0;
|
||||
let totalCount = 0;
|
||||
var productsTotal = 0;
|
||||
var totalCount = 0;
|
||||
|
||||
$('.products__item').each(function() {
|
||||
const price = parseInt($(this).data('price'));
|
||||
const quantity = parseInt($(this).find('.products__qty-value').text());
|
||||
var price = parseInt($(this).data('price'));
|
||||
var quantity = parseInt($(this).find('.products__qty-value').text());
|
||||
productsTotal += price * quantity;
|
||||
totalCount += quantity;
|
||||
});
|
||||
|
||||
const delivery = parseFloat($('input[name="delivery_price"]').val());
|
||||
const discount = parseFloat($('input[name="discount"]').val());
|
||||
const finalTotal = productsTotal + delivery - discount;
|
||||
var delivery = parseFloat($('input[name="delivery_price"]').val());
|
||||
var discount = parseFloat($('input[name="discount"]').val());
|
||||
var finalTotal = productsTotal + delivery - discount;
|
||||
|
||||
$('.products-total').text(productsTotal.toLocaleString('ru-RU') + ' ₽');
|
||||
$('.summary-count').text(totalCount);
|
||||
$('.final-total').text(finalTotal.toLocaleString('ru-RU') + ' ₽');
|
||||
}
|
||||
|
||||
// Промокод
|
||||
$('#applyPromo').click(function() {
|
||||
const promoCode = $('#promo_code').val().toUpperCase();
|
||||
var promoCode = $('#promo_code').val().toUpperCase();
|
||||
if (promoCode === 'SALE10') {
|
||||
const productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, ''));
|
||||
const discount = Math.round(productsTotal * 0.1);
|
||||
var productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, ''));
|
||||
var discount = Math.round(productsTotal * 0.1);
|
||||
$('input[name="discount"]').val(discount);
|
||||
$('.discount-total').text(discount.toLocaleString('ru-RU') + ' ₽');
|
||||
showNotification('Промокод применен! Скидка 10%');
|
||||
@@ -266,7 +273,6 @@ $(document).ready(function() {
|
||||
}
|
||||
});
|
||||
|
||||
// Оформление заказа
|
||||
$('#orderForm').submit(function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -278,7 +284,7 @@ $(document).ready(function() {
|
||||
$('#submit-order').prop('disabled', true).text('ОБРАБОТКА...');
|
||||
|
||||
$.ajax({
|
||||
url: '/cite_practica/order',
|
||||
url: '/order',
|
||||
method: 'POST',
|
||||
data: $(this).serialize(),
|
||||
dataType: 'json',
|
||||
@@ -286,7 +292,7 @@ $(document).ready(function() {
|
||||
if (result.success) {
|
||||
showNotification('Заказ успешно оформлен!');
|
||||
setTimeout(function() {
|
||||
window.location.href = '/cite_practica/';
|
||||
window.location.href = '/';
|
||||
}, 1500);
|
||||
} else {
|
||||
showNotification('Ошибка: ' + result.message, 'error');
|
||||
@@ -301,4 +307,3 @@ $(document).ready(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
К сожалению, запрошенная страница не существует или была перемещена.
|
||||
</p>
|
||||
<div style="margin-top: 30px;">
|
||||
<a href="/cite_practica/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
<a href="/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
<i class="fas fa-home"></i> На главную
|
||||
</a>
|
||||
<a href="/cite_practica/catalog" class="btn" style="display: inline-block; background: #617365; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none; margin-left: 10px;">
|
||||
<a href="/catalog" class="btn" style="display: inline-block; background: #617365; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none; margin-left: 10px;">
|
||||
<i class="fas fa-store"></i> В каталог
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
Произошла ошибка при обработке вашего запроса. Мы уже работаем над её устранением.
|
||||
</p>
|
||||
<div style="margin-top: 30px;">
|
||||
<a href="/cite_practica/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
<a href="/" class="btn primary-btn" style="display: inline-block; background: #453227; color: white; padding: 12px 30px; border-radius: 4px; text-decoration: none;">
|
||||
<i class="fas fa-home"></i> На главную
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -4,16 +4,16 @@
|
||||
<div class="container hero__content">
|
||||
<div class="hero__image-block">
|
||||
<div class="hero__circle"></div>
|
||||
<img src="/img/chair.PNG" alt="Кресло и торшер" class="hero__img">
|
||||
<img src="/assets/images/chair.PNG" alt="Кресло и торшер" class="hero__img">
|
||||
</div>
|
||||
<div class="hero__text-block">
|
||||
<h1>ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР</h1>
|
||||
<p class="hero__usp-text">Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.</p>
|
||||
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<a href="/cite_practica/catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
|
||||
<a href="/catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
|
||||
<?php else: ?>
|
||||
<a href="/cite_practica/login" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
|
||||
<a href="/login" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -44,17 +44,17 @@
|
||||
|
||||
<div class="promo-images">
|
||||
<div class="promo-image-col">
|
||||
<img src="/img/спальня.jpg" alt="Кровать и тумба">
|
||||
<img src="/assets/images/спальня.jpg" alt="Кровать и тумба">
|
||||
<div class="image-overlay-text">
|
||||
<h4>НОВИНКИ В КАТЕГОРИЯХ <br>МЯГКАЯ МЕБЕЛЬ</h4>
|
||||
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a>
|
||||
<a href="/catalog" class="overlay-link">ПЕРЕЙТИ</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="promo-image-col">
|
||||
<img src="/img/диван.jpg" alt="Диван в гостиной">
|
||||
<img src="/assets/images/диван.jpg" alt="Диван в гостиной">
|
||||
<div class="image-overlay-text">
|
||||
<h4>РАСПРОДАЖА <br>ПРЕДМЕТЫ ДЕКОРА</h4>
|
||||
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a>
|
||||
<a href="/catalog" class="overlay-link">ПЕРЕЙТИ</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -68,11 +68,11 @@
|
||||
<h2>О НАС</h2>
|
||||
<p class="text-justified">Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.</p>
|
||||
</div>
|
||||
<img src="/img/кресло_1.jpg" alt="Фиолетовое кресло" class="about__img about__img--small">
|
||||
<img src="/assets/images/кресло_1.jpg" alt="Фиолетовое кресло" class="about__img about__img--small">
|
||||
</div>
|
||||
|
||||
<div class="about__column about__column--right">
|
||||
<img src="/img/диван_1.jpg" alt="Белый диван с подушками" class="about__img about__img--large">
|
||||
<img src="/assets/images/диван_1.jpg" alt="Белый диван с подушками" class="about__img about__img--large">
|
||||
<p class="about__caption">Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -83,12 +83,20 @@
|
||||
<div class="solutions-slider">
|
||||
<div class="solutions-slider__slides">
|
||||
<div class="solutions-slider__slide">
|
||||
<img src="/img/слайдер_1.jpg" class="solution-img" alt="Готовое решение для гостиной">
|
||||
<img src="/assets/images/слайдер_1.jpg" class="solution-img" alt="Готовое решение для гостиной">
|
||||
<div class="solution-text-overlay">
|
||||
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ ГОСТИНОЙ</h2><br>
|
||||
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
|
||||
</div>
|
||||
<a href="/cite_practica/catalog" class="solution-image-link">Подробнее</a>
|
||||
<a href="/catalog" class="solution-image-link">Подробнее</a>
|
||||
</div>
|
||||
<div class="solutions-slider__slide">
|
||||
<img src="/assets/images/слайдер_6.jpg" class="solution-img" alt="Готовое решение для спальни">
|
||||
<div class="solution-text-overlay">
|
||||
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ СПАЛЬНИ</h2><br>
|
||||
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
|
||||
</div>
|
||||
<a href="/catalog" class="solution-image-link">Подробнее</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -150,4 +158,3 @@
|
||||
<button class="btn primary-btn">Задать вопрос</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -44,25 +44,25 @@
|
||||
<h1><i class="fas fa-user-shield"></i> Админ-панель AETERNA</h1>
|
||||
<div>
|
||||
<span><?= htmlspecialchars($user['email'] ?? 'Администратор') ?></span>
|
||||
<a href="/cite_practica/catalog" class="btn btn-primary" style="margin-left: 10px;">В каталог</a>
|
||||
<a href="/cite_practica/logout" class="btn btn-danger" style="margin-left: 10px;">Выйти</a>
|
||||
<a href="/catalog" class="btn btn-primary" style="margin-left: 10px;">В каталог</a>
|
||||
<a href="/logout" class="btn btn-danger" style="margin-left: 10px;">Выйти</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="admin-tabs">
|
||||
<a href="/cite_practica/admin" class="admin-tab <?= ($action ?? '') === 'dashboard' ? 'active' : '' ?>">
|
||||
<a href="/admin" class="admin-tab <?= ($action ?? '') === 'dashboard' ? 'active' : '' ?>">
|
||||
<i class="fas fa-tachometer-alt"></i> Дашборд
|
||||
</a>
|
||||
<a href="/cite_practica/admin/products" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/products') ? 'active' : '' ?>">
|
||||
<a href="/admin/products" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/products') ? 'active' : '' ?>">
|
||||
<i class="fas fa-box"></i> Товары
|
||||
</a>
|
||||
<a href="/cite_practica/admin/categories" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/categories') ? 'active' : '' ?>">
|
||||
<a href="/admin/categories" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/categories') ? 'active' : '' ?>">
|
||||
<i class="fas fa-tags"></i> Категории
|
||||
</a>
|
||||
<a href="/cite_practica/admin/orders" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/orders') ? 'active' : '' ?>">
|
||||
<a href="/admin/orders" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/orders') ? 'active' : '' ?>">
|
||||
<i class="fas fa-shopping-cart"></i> Заказы
|
||||
</a>
|
||||
<a href="/cite_practica/admin/users" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/users') ? 'active' : '' ?>">
|
||||
<a href="/admin/users" class="admin-tab <?= str_contains($_SERVER['REQUEST_URI'] ?? '', '/users') ? 'active' : '' ?>">
|
||||
<i class="fas fa-users"></i> Пользователи
|
||||
</a>
|
||||
</div>
|
||||
@@ -72,4 +72,3 @@
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -47,16 +47,16 @@
|
||||
<?= \App\Core\View::partial('footer') ?>
|
||||
|
||||
<script>
|
||||
function showNotification(message, type = 'success') {
|
||||
const notification = $('#notification');
|
||||
function showNotification(message, type) {
|
||||
type = type || 'success';
|
||||
var notification = $('#notification');
|
||||
notification.text(message);
|
||||
notification.removeClass('success error').addClass(type + ' show');
|
||||
setTimeout(function() { notification.removeClass('show'); }, 3000);
|
||||
}
|
||||
|
||||
$(document).ready(function() {
|
||||
// Обновляем счетчик корзины
|
||||
$.get('/cite_practica/cart/count', function(response) {
|
||||
$.get('/cart/count', function(response) {
|
||||
if (response.success) {
|
||||
$('.cart-count').text(response.cart_count);
|
||||
}
|
||||
@@ -65,4 +65,3 @@
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<main class="delivery-page">
|
||||
<div class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Доставка и оплата</span>
|
||||
<a href="/">Главная</a> • <span class="current-page">Доставка и оплата</span>
|
||||
</div>
|
||||
|
||||
<h1 style="color: #453227; margin: 30px 0;">Доставка и оплата</h1>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<main class="services-page">
|
||||
<div class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Услуги</span>
|
||||
<a href="/">Главная</a> • <span class="current-page">Услуги</span>
|
||||
</div>
|
||||
|
||||
<h1 style="color: #453227; margin: 30px 0;">Наши услуги</h1>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<main class="warranty-page">
|
||||
<div class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Гарантия и возврат</span>
|
||||
<a href="/">Главная</a> • <span class="current-page">Гарантия и возврат</span>
|
||||
</div>
|
||||
|
||||
<h1 style="color: #453227; margin: 30px 0;">Гарантия и возврат</h1>
|
||||
|
||||
@@ -7,17 +7,17 @@
|
||||
<div class="footer__col">
|
||||
<h5>ПОКУПАТЕЛЮ</h5>
|
||||
<ul>
|
||||
<li><a href="/cite_practica/catalog">Каталог</a></li>
|
||||
<li><a href="/cite_practica/services">Услуги</a></li>
|
||||
<li><a href="/catalog">Каталог</a></li>
|
||||
<li><a href="/services">Услуги</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer__col">
|
||||
<h5>ПОМОЩЬ</h5>
|
||||
<ul>
|
||||
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li>
|
||||
<li><a href="/cite_practica/warranty">Гарантия и возврат</a></li>
|
||||
<li><a href="/cite_practica/#faq">Ответы на вопросы</a></li>
|
||||
<li><a href="/delivery">Доставка и оплата</a></li>
|
||||
<li><a href="/warranty">Гарантия и возврат</a></li>
|
||||
<li><a href="/#faq">Ответы на вопросы</a></li>
|
||||
<li><a href="#footer">Контакты</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
@@ -42,7 +42,6 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="copyright">
|
||||
<p>© 2025 AETERNA. Все права защищены.</p>
|
||||
<p>© <?= date('Y') ?> AETERNA. Все права защищены.</p>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
|
||||
@@ -6,24 +6,24 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
<header class="header">
|
||||
<div class="header__top">
|
||||
<div class="container header__top-content">
|
||||
<a href="/cite_practica/" class="logo">AETERNA</a>
|
||||
<a href="/" class="logo">AETERNA</a>
|
||||
|
||||
<div class="search-catalog">
|
||||
<div class="catalog-dropdown">
|
||||
Все категории <span>▼</span>
|
||||
<div class="catalog-dropdown__menu">
|
||||
<ul>
|
||||
<li><a href="/cite_practica/catalog">Все товары</a></li>
|
||||
<li><a href="/cite_practica/catalog?category=1">Диваны</a></li>
|
||||
<li><a href="/cite_practica/catalog?category=2">Кровати</a></li>
|
||||
<li><a href="/cite_practica/catalog?category=3">Шкафы</a></li>
|
||||
<li><a href="/cite_practica/catalog?category=4">Стулья</a></li>
|
||||
<li><a href="/cite_practica/catalog?category=5">Столы</a></li>
|
||||
<li><a href="/catalog">Все товары</a></li>
|
||||
<li><a href="/catalog?category=1">Диваны</a></li>
|
||||
<li><a href="/catalog?category=2">Кровати</a></li>
|
||||
<li><a href="/catalog?category=3">Шкафы</a></li>
|
||||
<li><a href="/catalog?category=4">Стулья</a></li>
|
||||
<li><a href="/catalog?category=5">Столы</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="search-box">
|
||||
<form method="GET" action="/cite_practica/catalog" style="display: flex; width: 100%;">
|
||||
<form method="GET" action="/catalog" style="display: flex; width: 100%;">
|
||||
<input type="text" name="search" placeholder="Поиск товаров" style="border: none; width: 100%; padding: 10px;">
|
||||
<button type="submit" style="background: none; border: none; cursor: pointer;">
|
||||
<span class="search-icon"><i class="fas fa-search"></i></span>
|
||||
@@ -34,7 +34,7 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
|
||||
<div class="header__icons--top">
|
||||
<?php if ($isLoggedIn): ?>
|
||||
<a href="/cite_practica/cart" class="icon cart-icon">
|
||||
<a href="/cart" class="icon cart-icon">
|
||||
<i class="fas fa-shopping-cart"></i>
|
||||
<span class="cart-count">0</span>
|
||||
</a>
|
||||
@@ -68,17 +68,17 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
</div>
|
||||
|
||||
<ul class="user-profile-links">
|
||||
<li><a href="/cite_practica/cart"><i class="fas fa-shopping-bag"></i> Корзина</a></li>
|
||||
<li><a href="/cart"><i class="fas fa-shopping-bag"></i> Корзина</a></li>
|
||||
<?php if ($isAdmin): ?>
|
||||
<li><a href="/cite_practica/admin"><i class="fas fa-user-shield"></i> Админ-панель</a></li>
|
||||
<li><a href="/admin"><i class="fas fa-user-shield"></i> Админ-панель</a></li>
|
||||
<?php endif; ?>
|
||||
<li><a href="/cite_practica/logout" class="logout-link"><i class="fas fa-sign-out-alt"></i> Выйти</a></li>
|
||||
<li><a href="/logout" class="logout-link"><i class="fas fa-sign-out-alt"></i> Выйти</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<?php else: ?>
|
||||
<a href="/cite_practica/login" class="icon"><i class="far fa-user"></i></a>
|
||||
<a href="/cite_practica/login" style="font-size: 12px; color: #666; margin-left: 5px;">Войти</a>
|
||||
<a href="/login" class="icon"><i class="far fa-user"></i></a>
|
||||
<a href="/login" style="font-size: 12px; color: #666; margin-left: 5px;">Войти</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
@@ -87,7 +87,7 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
<div class="header__bottom">
|
||||
<div class="container header__bottom-content">
|
||||
<div class="catalog-menu">
|
||||
<a href="/cite_practica/catalog" class="catalog-link">
|
||||
<a href="/catalog" class="catalog-link">
|
||||
<span class="catalog-lines">☰</span>
|
||||
Каталог
|
||||
</a>
|
||||
@@ -95,10 +95,10 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
|
||||
<nav class="nav">
|
||||
<ul class="nav-list">
|
||||
<li><a href="/cite_practica/">Главная</a></li>
|
||||
<li><a href="/cite_practica/services">Услуги</a></li>
|
||||
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li>
|
||||
<li><a href="/cite_practica/warranty">Гарантия</a></li>
|
||||
<li><a href="/">Главная</a></li>
|
||||
<li><a href="/services">Услуги</a></li>
|
||||
<li><a href="/delivery">Доставка и оплата</a></li>
|
||||
<li><a href="/warranty">Гарантия</a></li>
|
||||
<li><a href="#footer">Контакты</a></li>
|
||||
</ul>
|
||||
</nav>
|
||||
@@ -106,4 +106,3 @@ $user = $user ?? \App\Core\View::currentUser();
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@ use App\Core\View;
|
||||
<main class="catalog-main">
|
||||
<div class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> • <span class="current-page">Каталог</span>
|
||||
<a href="/">Главная</a> • <span class="current-page">Каталог</span>
|
||||
</div>
|
||||
|
||||
<?php if (!empty($success)): ?>
|
||||
@@ -48,10 +48,10 @@ use App\Core\View;
|
||||
<i class="fas fa-user-shield"></i> Панель управления каталогом
|
||||
</h3>
|
||||
<div>
|
||||
<a href="/cite_practica/admin/products" class="admin-btn"><i class="fas fa-boxes"></i> Управление каталогом</a>
|
||||
<a href="/cite_practica/admin/products/add" class="admin-btn"><i class="fas fa-plus"></i> Добавить товар</a>
|
||||
<a href="/cite_practica/admin/categories" class="admin-btn"><i class="fas fa-tags"></i> Категории</a>
|
||||
<a href="/cite_practica/admin/orders" class="admin-btn"><i class="fas fa-shopping-cart"></i> Заказы</a>
|
||||
<a href="/admin/products" class="admin-btn"><i class="fas fa-boxes"></i> Управление каталогом</a>
|
||||
<a href="/admin/products/add" class="admin-btn"><i class="fas fa-plus"></i> Добавить товар</a>
|
||||
<a href="/admin/categories" class="admin-btn"><i class="fas fa-tags"></i> Категории</a>
|
||||
<a href="/admin/orders" class="admin-btn"><i class="fas fa-shopping-cart"></i> Заказы</a>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
@@ -67,14 +67,14 @@ use App\Core\View;
|
||||
|
||||
<div class="catalog-wrapper">
|
||||
<aside class="catalog-sidebar">
|
||||
<form method="GET" action="/cite_practica/catalog" id="filterForm">
|
||||
<form method="GET" action="/catalog" id="filterForm">
|
||||
<div class="filter-group">
|
||||
<h4 class="filter-title">КАТЕГОРИИ</h4>
|
||||
<ul class="filter-list">
|
||||
<li><a href="/cite_practica/catalog" class="<?= empty($filters['category_id']) ? 'active-category' : '' ?>">Все товары</a></li>
|
||||
<li><a href="/catalog" class="<?= empty($filters['category_id']) ? 'active-category' : '' ?>">Все товары</a></li>
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<li>
|
||||
<a href="/cite_practica/catalog?category=<?= $category['category_id'] ?>"
|
||||
<a href="/catalog?category=<?= $category['category_id'] ?>"
|
||||
class="<?= $filters['category_id'] == $category['category_id'] ? 'active-category' : '' ?>">
|
||||
<?= htmlspecialchars($category['name']) ?>
|
||||
</a>
|
||||
@@ -129,7 +129,7 @@ use App\Core\View;
|
||||
<?php if (!empty($filters['search'])): ?>
|
||||
<p style="color: #666;">
|
||||
Результаты поиска: "<strong><?= htmlspecialchars($filters['search']) ?></strong>"
|
||||
<a href="/cite_practica/catalog" style="margin-left: 10px; color: #617365;">
|
||||
<a href="/catalog" style="margin-left: 10px; color: #617365;">
|
||||
<i class="fas fa-times"></i> Очистить
|
||||
</a>
|
||||
</p>
|
||||
@@ -144,9 +144,19 @@ use App\Core\View;
|
||||
<?php else: ?>
|
||||
<?php foreach ($products as $product): ?>
|
||||
<div class="product-card <?= !$product['is_available'] ? 'unavailable' : '' ?>"
|
||||
onclick="window.location.href='/cite_practica/product/<?= $product['product_id'] ?>'"
|
||||
onclick="window.location.href='/product/<?= $product['product_id'] ?>'"
|
||||
data-product-id="<?= $product['product_id'] ?>">
|
||||
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
|
||||
<?php
|
||||
$imageUrl = $product['image_url'] ?? '';
|
||||
if (empty($imageUrl)) {
|
||||
$imageUrl = '/assets/images/1.jpg';
|
||||
} elseif (strpos($imageUrl, '/img2/') === 0) {
|
||||
$imageUrl = str_replace('/img2/', '/assets/images/', $imageUrl);
|
||||
} elseif (strpos($imageUrl, 'img2/') === 0) {
|
||||
$imageUrl = str_replace('img2/', '/assets/images/', $imageUrl);
|
||||
}
|
||||
?>
|
||||
<img src="<?= htmlspecialchars($imageUrl) ?>"
|
||||
alt="<?= htmlspecialchars($product['name']) ?>">
|
||||
<div class="product-info">
|
||||
<div class="name"><?= htmlspecialchars($product['name']) ?></div>
|
||||
@@ -174,7 +184,7 @@ $('#priceSlider').on('input', function() {
|
||||
|
||||
function addToCart(productId, productName) {
|
||||
$.ajax({
|
||||
url: '/cite_practica/cart/add',
|
||||
url: '/cart/add',
|
||||
method: 'POST',
|
||||
data: { product_id: productId, quantity: 1 },
|
||||
dataType: 'json',
|
||||
@@ -192,4 +202,3 @@ function addToCart(productId, productName) {
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -35,10 +35,10 @@ use App\Core\View;
|
||||
|
||||
<main class="container">
|
||||
<div class="breadcrumbs">
|
||||
<a href="/cite_practica/">Главная</a> •
|
||||
<a href="/cite_practica/catalog">Каталог</a> •
|
||||
<a href="/">Главная</a> •
|
||||
<a href="/catalog">Каталог</a> •
|
||||
<?php if ($product['category_name']): ?>
|
||||
<a href="/cite_practica/catalog?category=<?= $product['category_id'] ?>">
|
||||
<a href="/catalog?category=<?= $product['category_id'] ?>">
|
||||
<?= htmlspecialchars($product['category_name']) ?>
|
||||
</a> •
|
||||
<?php endif; ?>
|
||||
@@ -48,7 +48,17 @@ use App\Core\View;
|
||||
<div class="product__section">
|
||||
<div class="product__gallery">
|
||||
<div class="product__main-image">
|
||||
<img src="/cite_practica/<?= htmlspecialchars($product['image_url'] ?? 'img/1.jpg') ?>"
|
||||
<?php
|
||||
$imageUrl = $product['image_url'] ?? '';
|
||||
if (empty($imageUrl)) {
|
||||
$imageUrl = '/assets/images/1.jpg';
|
||||
} elseif (strpos($imageUrl, '/img2/') === 0) {
|
||||
$imageUrl = str_replace('/img2/', '/assets/images/', $imageUrl);
|
||||
} elseif (strpos($imageUrl, 'img2/') === 0) {
|
||||
$imageUrl = str_replace('img2/', '/assets/images/', $imageUrl);
|
||||
}
|
||||
?>
|
||||
<img src="<?= htmlspecialchars($imageUrl) ?>"
|
||||
alt="<?= htmlspecialchars($product['name']) ?>">
|
||||
</div>
|
||||
</div>
|
||||
@@ -134,7 +144,7 @@ use App\Core\View;
|
||||
|
||||
<?php if ($isAdmin): ?>
|
||||
<div style="margin-top: 20px;">
|
||||
<a href="/cite_practica/admin/products/edit/<?= $product['product_id'] ?>" class="btn" style="background: #ffc107; color: #333;">
|
||||
<a href="/admin/products/edit/<?= $product['product_id'] ?>" class="btn" style="background: #ffc107; color: #333;">
|
||||
<i class="fas fa-edit"></i> Редактировать
|
||||
</a>
|
||||
</div>
|
||||
@@ -147,8 +157,18 @@ use App\Core\View;
|
||||
<h2>Похожие товары</h2>
|
||||
<div class="products-grid">
|
||||
<?php foreach ($similarProducts as $similar): ?>
|
||||
<div class="product-card" onclick="window.location.href='/cite_practica/product/<?= $similar['product_id'] ?>'" style="cursor: pointer;">
|
||||
<img src="/cite_practica/<?= htmlspecialchars($similar['image_url'] ?? 'img/1.jpg') ?>"
|
||||
<?php
|
||||
$simImageUrl = $similar['image_url'] ?? '';
|
||||
if (empty($simImageUrl)) {
|
||||
$simImageUrl = '/assets/images/1.jpg';
|
||||
} elseif (strpos($simImageUrl, '/img2/') === 0) {
|
||||
$simImageUrl = str_replace('/img2/', '/assets/images/', $simImageUrl);
|
||||
} elseif (strpos($simImageUrl, 'img2/') === 0) {
|
||||
$simImageUrl = str_replace('img2/', '/assets/images/', $simImageUrl);
|
||||
}
|
||||
?>
|
||||
<div class="product-card" onclick="window.location.href='/product/<?= $similar['product_id'] ?>'" style="cursor: pointer;">
|
||||
<img src="<?= htmlspecialchars($simImageUrl) ?>"
|
||||
alt="<?= htmlspecialchars($similar['name']) ?>">
|
||||
<div class="product-info">
|
||||
<h3 style="font-size: 16px; color: #453227;"><?= htmlspecialchars($similar['name']) ?></h3>
|
||||
@@ -180,7 +200,7 @@ $(document).ready(function() {
|
||||
function addToCart(productId) {
|
||||
const quantity = $('.product__qty-value').val();
|
||||
$.ajax({
|
||||
url: '/cite_practica/cart/add',
|
||||
url: '/cart/add',
|
||||
method: 'POST',
|
||||
data: { product_id: productId, quantity: quantity },
|
||||
dataType: 'json',
|
||||
@@ -198,13 +218,12 @@ function addToCart(productId) {
|
||||
function buyNow(productId) {
|
||||
const quantity = $('.product__qty-value').val();
|
||||
$.ajax({
|
||||
url: '/cite_practica/cart/add',
|
||||
url: '/cart/add',
|
||||
method: 'POST',
|
||||
data: { product_id: productId, quantity: quantity },
|
||||
success: function() {
|
||||
window.location.href = '/cite_practica/cart';
|
||||
window.location.href = '/cart';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user