diff --git a/ init_db.php b/ init_db.php
deleted file mode 100644
index 901a438..0000000
--- a/ init_db.php
+++ /dev/null
@@ -1,122 +0,0 @@
-getConnection();
-
-// Создаем таблицы, если они не существуют
-$tables = [
- 'users' => "
- CREATE TABLE IF NOT EXISTS users (
- user_id SERIAL PRIMARY KEY,
- email VARCHAR(255) UNIQUE NOT NULL,
- password_hash VARCHAR(255) NOT NULL,
- full_name VARCHAR(100) NOT NULL,
- phone VARCHAR(20),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- is_active BOOLEAN DEFAULT TRUE,
- is_admin BOOLEAN DEFAULT FALSE
- )
- ",
-
- 'categories' => "
- CREATE TABLE IF NOT EXISTS categories (
- category_id SERIAL PRIMARY KEY,
- name VARCHAR(100) NOT NULL,
- slug VARCHAR(100) UNIQUE NOT NULL,
- parent_id INTEGER REFERENCES categories(category_id),
- description TEXT,
- sort_order INTEGER DEFAULT 0,
- is_active BOOLEAN DEFAULT TRUE
- )
- ",
-
- 'products' => "
- CREATE TABLE IF NOT EXISTS products (
- product_id SERIAL PRIMARY KEY,
- category_id INTEGER REFERENCES categories(category_id),
- name VARCHAR(200) NOT NULL,
- slug VARCHAR(200) UNIQUE NOT NULL,
- description TEXT,
- price DECIMAL(10, 2) NOT NULL,
- old_price DECIMAL(10, 2),
- sku VARCHAR(50) UNIQUE,
- stock_quantity INTEGER DEFAULT 0,
- is_available BOOLEAN DEFAULT TRUE,
- is_featured BOOLEAN DEFAULT FALSE,
- rating DECIMAL(3, 2) DEFAULT 0,
- review_count INTEGER DEFAULT 0,
- image_url VARCHAR(500),
- created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
- updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
- )
- "
-];
-
-foreach ($tables_to_create as $table_name => $sql) {
- try {
- $db->exec($sql);
- echo "Таблица '$table_name' создана/проверена
";
- } catch (PDOException $e) {
- echo "Ошибка создания таблицы '$table_name': " . $e->getMessage() . "
";
- }
-}
-
-// Добавляем тестовые данные
-// Проверяем, есть ли уже категории
-$check_categories = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn();
-
-if ($check_categories == 0) {
- // Добавляем категории
- $categories = [
- ['Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'],
- ['Диваны', 'divany', 1, 'Прямые и угловые диваны'],
- ['Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'],
- ['Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'],
- ['Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати']
- ];
-
- foreach ($categories as $category) {
- $stmt = $db->prepare("INSERT INTO categories (name, slug, parent_id, description) VALUES (?, ?, ?, ?)");
- $stmt->execute($category);
- }
- echo "Добавлены категории
";
-}
-
-// Проверяем, есть ли уже товары
-$check_products = $db->query("SELECT COUNT(*) FROM products")->fetchColumn();
-
-if ($check_products == 0) {
- // Добавляем товары
- $products = [
- [2, 'Диван VELVET', 'divan-velvet', 'Прямой диван с тканевой обивкой', 45999, 54999, 'DIV-VEL-001', 10],
- [2, 'Диван MODERN', 'divan-modern', 'Угловой диван с кожаной обивкой', 78999, 89999, 'DIV-MOD-002', 5],
- [3, 'Кресло OPPORTUNITY', 'kreslo-opportunity', 'Кресло с деревянными ножками', 16999, 19999, 'KRES-OPP-001', 15],
- [3, 'Кресло GOLDEN', 'kreslo-golden', 'Золотистое кресло для гостиной', 19999, 23999, 'KRES-GOL-002', 8],
- [5, 'Кровать CLASSIC', 'krovat-classic', 'Двуспальная кровать из массива дуба', 64999, 74999, 'KROV-CLA-001', 3]
- ];
-
- foreach ($products as $product) {
- $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity)
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
- $stmt->execute($product);
- }
- echo "Добавлены товары
";
-}
-
-// Проверяем, есть ли администратор
-$check_admin = $db->prepare("SELECT COUNT(*) FROM users WHERE email = ?");
-$check_admin->execute(['admin@aeterna.ru']);
-
-if ($check_admin->fetchColumn() == 0) {
- // Добавляем администратора (пароль: admin123)
- $admin_password = password_hash('admin123', PASSWORD_DEFAULT);
- $stmt = $db->prepare("INSERT INTO users (email, password_hash, full_name, phone, is_admin)
- VALUES (?, ?, ?, ?, ?)");
- $stmt->execute(['admin@aeterna.ru', $admin_password, 'Администратор AETERNA', '+79129991223', true]);
- echo "Добавлен администратор (email: admin@aeterna.ru, пароль: admin123)
";
-}
-
-echo "
База данных успешно инициализирована!
";
-echo "Перейти в каталог";
-?>
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 9c84dff..9952a9f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,49 +1,46 @@
-# IDE и редакторы
+# Dependencies
+/vendor/
+/node_modules/
+
+# IDE
.idea/
.vscode/
*.swp
*.swo
*~
+
+# OS
.DS_Store
+Thumbs.db
-# Зависимости
-/vendor/
-/node_modules/
-
-# Логи
-*.log
-logs/
-
-# Загруженные файлы пользователей
-/uploads/products/*
-!/uploads/products/.gitkeep
-
-# Конфигурационные файлы с секретами (если есть)
+# Environment
.env
.env.local
.env.*.local
-# Кэш
+# Logs
+*.log
+/logs/
+
+# Storage (uploads are gitignored, keep structure)
+/storage/uploads/*
+!/storage/uploads/.gitkeep
+
+# Cache
/cache/
*.cache
-# Временные файлы
+# Compiled assets
+/public/assets/css/*.css
+!/public/assets/css/.gitkeep
+
+# Docker volumes
+/docker/data/
+
+# Tests
+/coverage/
+.phpunit.result.cache
+
+# Temporary files
/tmp/
*.tmp
-
-# Скомпилированные CSS
-*.css.map
-
-# База данных SQLite (если используется локально)
-*.db
-*.sqlite
-*.sqlite3
-
-# Файлы резервных копий
-*.bak
-*.backup
-
-# PHP debug/profiling
-.phpunit.result.cache
-phpunit.xml
-
diff --git a/.htaccess b/.htaccess
deleted file mode 100644
index 2c0090b..0000000
--- a/.htaccess
+++ /dev/null
@@ -1,29 +0,0 @@
-# AETERNA MVC - Корневой .htaccess
-# Перенаправляет все запросы в public/
-
-
- RewriteEngine On
-
- # Если запрос к assets (статика) - перенаправляем в public/assets
- RewriteCond %{REQUEST_URI} ^/cite_practica/assets/
- RewriteRule ^assets/(.*)$ public/assets/$1 [L]
-
- # Если запрос к старым img директориям - оставляем как есть
- RewriteCond %{REQUEST_URI} ^/cite_practica/img/
- RewriteRule ^img/(.*)$ img/$1 [L]
-
- RewriteCond %{REQUEST_URI} ^/cite_practica/img2/
- RewriteRule ^img2/(.*)$ img2/$1 [L]
-
- # Если это существующий файл (для обратной совместимости) - пропускаем
- RewriteCond %{REQUEST_FILENAME} -f
- RewriteRule ^ - [L]
-
- # Все остальное - в public/index.php
- RewriteCond %{REQUEST_FILENAME} !-d
- RewriteRule ^(.*)$ public/index.php [QSA,L]
-
-
-# Отключаем просмотр директорий
-Options -Indexes
-
diff --git a/Dockerfile b/Dockerfile
index 75e71ec..1433ae0 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,5 @@
FROM php:8.2-apache
-# Установка расширений PHP
RUN apt-get update && apt-get install -y \
libpq-dev \
libzip-dev \
@@ -9,26 +8,22 @@ RUN apt-get update && apt-get install -y \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
-# Включаем mod_rewrite
-RUN a2enmod rewrite
+RUN a2enmod rewrite headers expires
-# Копируем конфигурацию Apache
-COPY docker/apache/vhosts.conf /etc/apache2/sites-available/vhosts.conf
+COPY docker/apache/vhosts.conf /etc/apache2/sites-available/000-default.conf
COPY docker/apache/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh
-# Рабочая директория
WORKDIR /var/www/html
-# Копируем приложение
COPY . /var/www/html/
-# Устанавливаем права
-RUN chown -R www-data:www-data /var/www/html
+RUN mkdir -p /var/www/html/storage/uploads \
+ && mkdir -p /var/www/html/public/assets/css
+
+RUN chown -R www-data:www-data /var/www/html \
+ && chmod -R 755 /var/www/html/storage
-# Экспортируем порт
EXPOSE 80
-# Точка входа
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..548eab1
--- /dev/null
+++ b/README.md
@@ -0,0 +1,171 @@
+# AETERNA - Интернет-магазин мебели
+
+Современный интернет-магазин мебели на PHP с MVC архитектурой.
+
+## Структура проекта
+
+```
+aeterna/
+├── app/ # Приложение
+│ ├── Controllers/ # Контроллеры
+│ ├── Core/ # Ядро (App, Router, View, etc.)
+│ ├── Models/ # Модели
+│ └── Views/ # Шаблоны
+├── config/ # Конфигурация
+│ ├── app.php # Настройки приложения
+│ ├── database.php # Настройки БД
+│ └── routes.php # Маршруты
+├── public/ # Публичная директория (DocumentRoot)
+│ ├── index.php # Точка входа
+│ ├── assets/ # Статические файлы
+│ │ ├── css/
+│ │ ├── js/
+│ │ └── images/ # Изображения
+│ └── .htaccess # Правила Apache
+├── storage/ # Хранилище
+│ └── uploads/ # Загруженные файлы
+├── tests/ # Тесты
+├── docker/ # Docker конфигурация
+│ └── apache/
+├── docker-compose.yml
+├── Dockerfile
+└── README.md
+```
+
+## Требования
+
+- PHP 8.2+
+- PostgreSQL 14+
+- Apache с mod_rewrite или Nginx
+- Docker (опционально)
+
+## Установка
+
+### Вариант 1: Docker (рекомендуется)
+
+```bash
+# Клонировать репозиторий
+git clone
+cd aeterna
+
+# Запустить контейнеры
+docker-compose up -d
+
+# Приложение будет доступно по адресу:
+# http://localhost:8080
+```
+
+### Вариант 2: Локальный сервер
+
+#### Apache
+
+1. Настройте DocumentRoot на директорию `public/`
+
+```apache
+
+ ServerName aeterna.local
+ DocumentRoot /path/to/aeterna/public
+
+
+ Options -Indexes +FollowSymLinks
+ AllowOverride All
+ Require all granted
+
+
+```
+
+2. Включите mod_rewrite:
+```bash
+sudo a2enmod rewrite
+sudo systemctl restart apache2
+```
+
+#### Nginx
+
+```nginx
+server {
+ listen 80;
+ server_name aeterna.local;
+ root /path/to/aeterna/public;
+ index index.php;
+
+ location / {
+ try_files $uri $uri/ /index.php?$query_string;
+ }
+
+ location ~ \.php$ {
+ fastcgi_pass unix:/var/run/php/php8.2-fpm.sock;
+ fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
+ include fastcgi_params;
+ }
+
+ location ~ /\.(?!well-known).* {
+ deny all;
+ }
+}
+```
+
+## Конфигурация
+
+### База данных
+
+Отредактируйте `config/database.php` или используйте переменные окружения:
+
+```bash
+export DB_HOST=localhost
+export DB_PORT=5432
+export DB_DATABASE=aeterna
+export DB_USERNAME=user
+export DB_PASSWORD=password
+```
+
+### Приложение
+
+Отредактируйте `config/app.php`:
+
+- `debug` - режим отладки (false для продакшена)
+- `url` - URL приложения
+- `admin_emails` - email адреса администраторов
+
+## Функционал
+
+### Для покупателей
+- Каталог товаров с фильтрацией
+- Корзина покупок
+- Оформление заказов
+- Регистрация и авторизация
+
+### Для администраторов
+- Управление товарами
+- Управление категориями
+- Управление заказами
+- Управление пользователями
+
+## Маршруты
+
+| Метод | URL | Описание |
+|-------|-----|----------|
+| GET | `/` | Главная страница |
+| GET | `/catalog` | Каталог товаров |
+| GET | `/product/{id}` | Страница товара |
+| GET | `/cart` | Корзина |
+| GET | `/login` | Вход |
+| GET | `/register` | Регистрация |
+| GET | `/admin` | Админ-панель |
+
+## Технологии
+
+- **Backend**: PHP 8.2, MVC архитектура
+- **Database**: PostgreSQL
+- **Frontend**: HTML5, CSS3/LESS, JavaScript, jQuery
+- **Сервер**: Apache/Nginx
+- **Контейнеризация**: Docker
+
+## Лицензия
+
+MIT License
+
+## Автор
+
+AETERNA Team
+
diff --git a/add_to_cart.php b/add_to_cart.php
deleted file mode 100644
index 4ff29d8..0000000
--- a/add_to_cart.php
+++ /dev/null
@@ -1,116 +0,0 @@
- false, 'message' => 'Требуется авторизация']);
- exit();
-}
-
-if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) {
- $product_id = intval($_POST['product_id']);
- $quantity = intval($_POST['quantity'] ?? 1);
- $user_id = $_SESSION['user_id'] ?? 0;
-
- if ($user_id == 0) {
- echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
- exit();
- }
-
- $db = Database::getInstance()->getConnection();
-
- try {
- // Проверяем наличие товара на складе
- $checkStock = $db->prepare("
- SELECT stock_quantity, name, price
- FROM products
- WHERE product_id = ? AND is_available = TRUE
- ");
- $checkStock->execute([$product_id]);
- $product = $checkStock->fetch();
-
- if (!$product) {
- echo json_encode(['success' => false, 'message' => 'Товар не найден']);
- exit();
- }
-
- if ($product['stock_quantity'] < $quantity) {
- echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']);
- exit();
- }
-
- // Проверяем, есть ли товар уже в корзине пользователя
- $checkCart = $db->prepare("
- SELECT cart_id, quantity
- FROM cart
- WHERE user_id = ? AND product_id = ?
- ");
- $checkCart->execute([$user_id, $product_id]);
- $cartItem = $checkCart->fetch();
-
- if ($cartItem) {
- // Обновляем количество
- $newQuantity = $cartItem['quantity'] + $quantity;
-
- // Проверяем общее количество
- if ($newQuantity > $product['stock_quantity']) {
- echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']);
- exit();
- }
-
- $updateStmt = $db->prepare("
- UPDATE cart
- SET quantity = ?, updated_at = CURRENT_TIMESTAMP
- WHERE cart_id = ?
- ");
- $updateStmt->execute([$newQuantity, $cartItem['cart_id']]);
- } else {
- // Добавляем новый товар
- $insertStmt = $db->prepare("
- INSERT INTO cart (user_id, product_id, quantity)
- VALUES (?, ?, ?)
- ");
- $insertStmt->execute([$user_id, $product_id, $quantity]);
- }
-
- // Обновляем сессию
- if (!isset($_SESSION['cart'])) {
- $_SESSION['cart'] = [];
- }
-
- if (isset($_SESSION['cart'][$product_id])) {
- $_SESSION['cart'][$product_id]['quantity'] += $quantity;
- } else {
- $_SESSION['cart'][$product_id] = [
- 'quantity' => $quantity,
- 'name' => $product['name'],
- 'price' => $product['price'],
- 'added_at' => time()
- ];
- }
-
- // Получаем общее количество товаров в корзине
- $cartCountStmt = $db->prepare("
- SELECT SUM(quantity) as total
- FROM cart
- WHERE user_id = ?
- ");
- $cartCountStmt->execute([$user_id]);
- $cart_count = $cartCountStmt->fetchColumn() ?: 0;
-
- echo json_encode([
- 'success' => true,
- 'cart_count' => $cart_count,
- 'message' => 'Товар добавлен в корзину'
- ]);
-
- } catch (PDOException $e) {
- echo json_encode([
- 'success' => false,
- 'message' => 'Ошибка базы данных: ' . $e->getMessage()
- ]);
- }
-} else {
- echo json_encode(['success' => false, 'message' => 'Неверный запрос']);
-}
-?>
\ No newline at end of file
diff --git a/admin_actions.php b/admin_actions.php
deleted file mode 100644
index ea9563b..0000000
--- a/admin_actions.php
+++ /dev/null
@@ -1,170 +0,0 @@
-getConnection();
-$action = $_GET['action'] ?? '';
-
-try {
- switch ($action) {
- case 'delete_product':
- if (isset($_GET['id'])) {
- $productId = intval($_GET['id']);
- // Делаем товар недоступным
- $stmt = $db->prepare("
- UPDATE products
- SET is_available = FALSE, stock_quantity = 0, updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
- $stmt->execute([$productId]);
- header('Location: admin_panel.php?action=products&message=Товар помечен как недоступный');
- exit();
- }
- break;
-
- case 'restore_product':
- if (isset($_GET['id'])) {
- $productId = intval($_GET['id']);
- // Восстанавливаем товар
- $stmt = $db->prepare("
- UPDATE products
- SET is_available = TRUE, stock_quantity = 10, updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
- $stmt->execute([$productId]);
- header('Location: admin_panel.php?action=products&message=Товар восстановлен');
- exit();
- }
- break;
-
- case 'delete_category':
- if (isset($_GET['id'])) {
- $categoryId = intval($_GET['id']);
-
- try {
- // 1. Проверяем, есть ли товары в этой категории
- $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
- $checkProducts->execute([$categoryId]);
- $productCount = $checkProducts->fetchColumn();
-
- if ($productCount > 0) {
- // Если есть товары, делаем категорию неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- header('Location: admin_panel.php?action=categories&message=Категория скрыта (содержит товары)');
- exit();
- }
-
- // 2. Проверяем, есть ли дочерние категории
- $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?");
- $checkChildren->execute([$categoryId]);
- $childCount = $checkChildren->fetchColumn();
-
- if ($childCount > 0) {
- // Вариант A: Делаем категорию неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- header('Location: admin_panel.php?action=categories&message=Категория скрыта (имеет дочерние категории)');
- exit();
-
- // Вариант B: Удаляем вместе с дочерними (раскомментируйте если нужно)
- /*
- // Сначала удаляем дочерние категории
- $stmt = $db->prepare("DELETE FROM categories WHERE parent_id = ?");
- $stmt->execute([$categoryId]);
-
- // Затем удаляем саму категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория и её дочерние категории удалены');
- exit();
- */
- }
-
- // 3. Если нет товаров и дочерних категорий, удаляем
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория удалена');
- exit();
-
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
- break;
-
- case 'delete_category_force':
- // Принудительное удаление с дочерними категориями
- if (isset($_GET['id'])) {
- $categoryId = intval($_GET['id']);
-
- try {
- // Сначала перемещаем товары в другую категорию (например, в первую)
- $firstCategory = $db->query("SELECT category_id FROM categories WHERE category_id != ? LIMIT 1")->fetchColumn();
-
- if ($firstCategory) {
- $moveProducts = $db->prepare("UPDATE products SET category_id = ? WHERE category_id = ?");
- $moveProducts->execute([$firstCategory, $categoryId]);
- }
-
- // Обнуляем parent_id у дочерних категорий
- $stmt = $db->prepare("UPDATE categories SET parent_id = NULL WHERE parent_id = ?");
- $stmt->execute([$categoryId]);
-
- // Удаляем категорию
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория удалена. Товары перемещены.');
- exit();
-
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
- break;
-
- case 'toggle_user':
- if (isset($_GET['id'])) {
- $userId = intval($_GET['id']);
- $stmt = $db->prepare("
- UPDATE users
- SET is_active = NOT is_active, updated_at = CURRENT_TIMESTAMP
- WHERE user_id = ?
- ");
- $stmt->execute([$userId]);
- header('Location: admin_panel.php?action=users&message=Статус пользователя изменен');
- exit();
- }
- break;
-
- case 'make_admin':
- if (isset($_GET['id'])) {
- $userId = intval($_GET['id']);
- $stmt = $db->prepare("UPDATE users SET is_admin = TRUE WHERE user_id = ?");
- $stmt->execute([$userId]);
- header('Location: admin_panel.php?action=users&message=Пользователь назначен администратором');
- exit();
- }
- break;
- }
-} catch (PDOException $e) {
- header('Location: admin_panel.php?error=' . urlencode($e->getMessage()));
- exit();
-}
-
-// Если действие не распознано
-header('Location: admin_panel.php');
-exit();
-?>
\ No newline at end of file
diff --git a/admin_panel.php b/admin_panel.php
deleted file mode 100644
index f8a4252..0000000
--- a/admin_panel.php
+++ /dev/null
@@ -1,772 +0,0 @@
-Сначала добавьте категории!';
-}
-
-// Проверка прав администратора
-if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
- echo "";
- exit();
-}
-
-$db = Database::getInstance()->getConnection();
-
-// Обработка действий
-$action = $_GET['action'] ?? 'dashboard';
-$message = $_GET['message'] ?? '';
-$error = $_GET['error'] ?? '';
-
-// Обработка POST запросов - ДОБАВЛЕНО ПРОСТОЕ И РАБОТАЮЩЕЕ!
-if ($_SERVER['REQUEST_METHOD'] === 'POST') {
- $post_action = $_POST['action'] ?? '';
-
- try {
- if ($post_action === 'add_category') {
- $name = trim($_POST['name'] ?? '');
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
- $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
- $description = trim($_POST['description'] ?? '');
- $sort_order = (int)($_POST['sort_order'] ?? 0);
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- if (empty($name)) {
- throw new Exception('Название категории обязательно');
- }
-
- $stmt = $db->prepare("
- INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
- VALUES (?, ?, ?, ?, ?, ?)
- ");
-
- $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
-
- if ($result) {
- header('Location: admin_panel.php?action=categories&message=Категория+успешно+добавлена');
- exit();
- }
- }
-
- // ИСПРАВЬТЕ БЛОК edit_category или добавьте его если его нет:
- if ($post_action === 'edit_category' && isset($_POST['category_id'])) {
- $category_id = (int)$_POST['category_id'];
- $name = trim($_POST['name'] ?? '');
- $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL;
- $description = trim($_POST['description'] ?? '');
- $sort_order = (int)($_POST['sort_order'] ?? 0);
- $is_active = isset($_POST['is_active']) ? 1 : 0;
-
- if (empty($name)) {
- throw new Exception('Название категории обязательно');
- }
-
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
-
- $stmt = $db->prepare("
- UPDATE categories SET
- name = ?,
- slug = ?,
- parent_id = ?,
- description = ?,
- sort_order = ?,
- is_active = ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE category_id = ?
- ");
-
- $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+обновлена');
- exit();
- }
-
- if ($post_action === 'add_product') {
- $name = trim($_POST['name'] ?? '');
- $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
- $category_id = (int)($_POST['category_id'] ?? 0);
- $description = trim($_POST['description'] ?? '');
- $price = (float)($_POST['price'] ?? 0);
- $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
- $sku = trim($_POST['sku'] ?? '');
- $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
- $is_available = isset($_POST['is_available']) ? 1 : 0;
- $is_featured = isset($_POST['is_featured']) ? 1 : 0;
- $image_url = trim($_POST['image_url'] ?? '');
- $color = trim($_POST['color'] ?? '');
- $material = trim($_POST['material'] ?? '');
- $card_size = trim($_POST['card_size'] ?? 'small');
-
-
- // ВАЖНО: Проверяем category_id
- if ($category_id <= 0) {
- $_SESSION['error'] = 'Выберите корректную категорию';
- header('Location: admin_panel.php?action=add_product');
- exit();
- }
-
- // Проверяем существование категории
- $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?");
- $check_category->execute([$category_id]);
- if ($check_category->fetchColumn() == 0) {
- $_SESSION['error'] = 'Выбранная категория не существует';
- header('Location: admin_panel.php?action=add_product');
- exit();
- }
-
- if (empty($name)) throw new Exception('Название товара обязательно');
- if ($price <= 0) throw new Exception('Цена должна быть больше 0');
-
- // Генерируем SKU если пустой
- if (empty($sku)) {
- $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999);
- }
-
- $stmt = $db->prepare("
- INSERT INTO products (
- category_id, name, slug, description, price, old_price,
- sku, stock_quantity, is_available, is_featured, image_url,
- color, material, card_size
- ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
- ");
-
- $result = $stmt->execute([
- $category_id, $name, $slug, $description, $price, $old_price,
- $sku, $stock_quantity, $is_available, $is_featured, $image_url,
- $color, $material, $card_size
- ]);
-
- if ($result) {
- $_SESSION['message'] = 'Товар успешно добавлен';
- header('Location: admin_panel.php?action=products');
- exit();
- }
- }
-
- // ИСПРАВЛЕННЫЙ КОД для edit_product в admin_panel.php:
- if ($post_action === 'edit_product' && isset($_POST['product_id'])) {
- $product_id = (int)$_POST['product_id'];
- $name = trim($_POST['name'] ?? '');
- $category_id = (int)($_POST['category_id'] ?? 1); // ПО УМОЛЧАНИЮ 1, чтобы избежать 0
- $description = trim($_POST['description'] ?? '');
- $price = (float)($_POST['price'] ?? 0);
- $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL;
- $stock_quantity = (int)($_POST['stock_quantity'] ?? 0);
- $is_available = isset($_POST['is_available']) ? 1 : 0;
- $image_url = trim($_POST['image_url'] ?? '');
- $color = trim($_POST['color'] ?? '');
- $material = trim($_POST['material'] ?? '');
-
- // ВАЖНО: Проверяем category_id
- if ($category_id <= 0) {
- // Если category_id = 0, устанавливаем первую доступную категорию
- $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn();
- $category_id = $firstCat ?: 1;
- }
-
- $stmt = $db->prepare("
- UPDATE products SET
- name = ?,
- category_id = ?,
- description = ?,
- price = ?,
- old_price = ?,
- stock_quantity = ?,
- is_available = ?,
- image_url = ?,
- color = ?,
- material = ?,
- updated_at = CURRENT_TIMESTAMP
- WHERE product_id = ?
- ");
-
- $stmt->execute([
- $name, $category_id, $description, $price, $old_price,
- $stock_quantity, $is_available, $image_url, $color, $material, $product_id
- ]);
-
- header('Location: admin_panel.php?action=products&message=Товар+обновлен');
- exit();
- }
-
- if ($post_action === 'delete_category' && isset($_POST['category_id'])) {
- $categoryId = intval($_POST['category_id']);
-
- // 1. Проверяем, есть ли товары в этой категории
- $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?");
- $checkProducts->execute([$categoryId]);
- $productCount = $checkProducts->fetchColumn();
-
- // 2. Проверяем, есть ли дочерние категории
- $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?");
- $checkChildren->execute([$categoryId]);
- $childCount = $checkChildren->fetchColumn();
-
- if ($productCount > 0) {
- // Если есть товары, делаем категорию неактивной вместо удаления
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(содержит+товары)');
- exit();
- } elseif ($childCount > 0) {
- // Если есть дочерние категории, делаем неактивной
- $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)');
- exit();
- } else {
- // Если нет товаров и дочерних категорий, удаляем
- $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
-
- header('Location: admin_panel.php?action=categories&message=Категория+удалена');
- exit();
- }
- }
- } catch (PDOException $e) {
- header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage()));
- exit();
- } catch (Exception $e) {
- header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode($e->getMessage()));
- exit();
- }
- }
-
-
-// Получение данных для отображения
-try {
- // Статистика
- $stats = [
- 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(),
- 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(),
- 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(),
- 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(),
- 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn()
- ];
-
- // Получаем все категории
- $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll();
-
- // Получаем родительские категории
- $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll();
-
- switch ($action) {
- case 'products':
- $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1';
- $sql = $showAll
- ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC"
- : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC";
- $data = $db->query($sql)->fetchAll();
- break;
-
- case 'categories':
- $data = $db->query("
- SELECT c1.*, c2.name as parent_name,
- (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count
- FROM categories c1
- LEFT JOIN categories c2 ON c1.parent_id = c2.category_id
- ORDER BY c1.sort_order, c1.name
- ")->fetchAll();
- break;
-
- case 'orders':
- $data = $db->query("
- SELECT o.*, u.email as user_email
- FROM orders o
- LEFT JOIN users u ON o.user_id = u.user_id
- ORDER BY o.created_at DESC
- LIMIT 50
- ")->fetchAll();
- break;
-
- case 'users':
- $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll();
- break;
-
- case 'add_product':
- case 'edit_product':
- if ($action === 'edit_product' && isset($_GET['id'])) {
- $productId = (int)$_GET['id'];
- $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
- $stmt->execute([$productId]);
- $edit_data = $stmt->fetch();
- }
- break;
-
- case 'add_category':
- case 'edit_category':
- if ($action === 'edit_category' && isset($_GET['id'])) {
- $categoryId = (int)$_GET['id'];
- $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?");
- $stmt->execute([$categoryId]);
- $edit_data = $stmt->fetch();
- }
- break;
-
- }
-
-} catch (PDOException $e) {
- $error = "Ошибка базы данных: " . $e->getMessage();
-}
-?>
-
-
-
-
-
- AETERNA - Админ-панель
-
-
-
-
-
-
-
-
-
-
-
- = htmlspecialchars(urldecode($message)) ?>
-
-
-
-
-
- = htmlspecialchars(urldecode($error)) ?>
-
-
-
-
-
-
Статистика
-
-
-
= $stats['total_products'] ?>
-
Всего товаров
-
-
-
= $stats['active_products'] ?>
-
Активных товаров
-
-
-
= $stats['total_orders'] ?>
-
Заказов
-
-
-
= $stats['total_users'] ?>
-
Пользователей
-
-
-
-
-
-
-
-
-
Управление товарами
-
-
-
-
-
-
- | ID |
- Название |
- Категория |
- Цена |
- На складе |
- Статус |
- Действия |
-
-
-
-
-
- | = $product['product_id'] ?> |
- = htmlspecialchars($product['name']) ?> |
- = htmlspecialchars($product['category_name'] ?? 'Без категории') ?> |
- = number_format($product['price'], 0, '', ' ') ?> ₽ |
- = $product['stock_quantity'] ?> |
-
- 0): ?>
- ✓ Доступен
-
- ✗ Недоступен
-
- ⚠ Нет на складе
-
- |
-
-
-
-
-
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
- | ID |
- Название |
- Slug |
- Родительская |
- Товаров |
- Действия |
-
-
-
-
-
- | = $category['category_id'] ?> |
- = htmlspecialchars($category['name']) ?> |
- = htmlspecialchars($category['slug']) ?> |
- = htmlspecialchars($category['parent_name'] ?? '—') ?> |
- = $category['product_count'] ?> |
-
-
-
- Редактировать
-
-
-
-
- |
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
Заказы
-
-
-
- | № заказа |
- Клиент |
- Сумма |
- Статус |
- Дата |
- Действия |
-
-
-
-
-
- | = htmlspecialchars($order['order_number']) ?> |
- = htmlspecialchars($order['customer_name']) ?> |
- = number_format($order['final_amount'], 0, '', ' ') ?> ₽ |
- = htmlspecialchars($order['status']) ?> |
- = date('d.m.Y H:i', strtotime($order['created_at'])) ?> |
-
-
-
-
- |
-
-
-
-
-
-
-
-
Пользователи
-
-
-
- | ID |
- Email |
- ФИО |
- Дата регистрации |
- Статус |
-
-
-
-
-
- | = $user['user_id'] ?> |
- = htmlspecialchars($user['email']) ?> |
- = htmlspecialchars($user['full_name']) ?> |
- = date('d.m.Y', strtotime($user['created_at'])) ?> |
-
-
- ✓ Активен
-
- ✗ Неактивен
-
- |
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php
index 3053536..f876cd6 100644
--- a/app/Controllers/AdminController.php
+++ b/app/Controllers/AdminController.php
@@ -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');
}
}
-
diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php
index 210794e..52d8a15 100644
--- a/app/Controllers/AuthController.php
+++ b/app/Controllers/AuthController.php
@@ -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();
}
}
-
diff --git a/app/Controllers/CartController.php b/app/Controllers/CartController.php
index d3368cc..6176046 100644
--- a/app/Controllers/CartController.php
+++ b/app/Controllers/CartController.php
@@ -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
]);
}
}
-
diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php
index af41ca7..cf86f92 100644
--- a/app/Controllers/HomeController.php
+++ b/app/Controllers/HomeController.php
@@ -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
]);
}
}
-
diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php
index 01c8a2a..bf38d9c 100644
--- a/app/Controllers/OrderController.php
+++ b/app/Controllers/OrderController.php
@@ -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
}
}
}
-
diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php
index 255a26a..a9faf44 100644
--- a/app/Controllers/PageController.php
+++ b/app/Controllers/PageController.php
@@ -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
]);
}
}
-
diff --git a/app/Controllers/ProductController.php b/app/Controllers/ProductController.php
index e2b34e5..68683ad 100644
--- a/app/Controllers/ProductController.php
+++ b/app/Controllers/ProductController.php
@@ -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
]);
}
}
-
diff --git a/app/Core/App.php b/app/Core/App.php
index f39c329..187d5d0 100644
--- a/app/Core/App.php
+++ b/app/Core/App.php
@@ -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 "Ошибка приложения
";
echo "Сообщение: " . htmlspecialchars($e->getMessage()) . "
";
echo "Файл: " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "
";
@@ -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
}
}
}
-
diff --git a/app/Core/Controller.php b/app/Core/Controller.php
index 125f52a..a479b6c 100644
--- a/app/Core/Controller.php
+++ b/app/Core/Controller.php
@@ -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;
}
}
-
diff --git a/app/Core/Database.php b/app/Core/Database.php
index 4aa2af6..2d553f3 100644
--- a/app/Core/Database.php
+++ b/app/Core/Database.php
@@ -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 запрещена");
}
}
-
diff --git a/app/Core/Model.php b/app/Core/Model.php
index 8063e7a..672389a 100644
--- a/app/Core/Model.php
+++ b/app/Core/Model.php
@@ -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);
}
}
-
diff --git a/app/Core/Router.php b/app/Core/Router.php
index 44b1a03..7180f88 100644
--- a/app/Core/Router.php
+++ b/app/Core/Router.php
@@ -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']);
}
}
-
diff --git a/app/Core/View.php b/app/Core/View.php
index 4e975f3..ef3c09d 100644
--- a/app/Core/View.php
+++ b/app/Core/View.php
@@ -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, '/');
}
}
-
diff --git a/app/Models/Cart.php b/app/Models/Cart.php
index 757c37c..cf746a2 100644
--- a/app/Models/Cart.php
+++ b/app/Models/Cart.php
@@ -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
]);
}
}
-
diff --git a/app/Models/Category.php b/app/Models/Category.php
index 54d659d..80d2bed 100644
--- a/app/Models/Category.php
+++ b/app/Models/Category.php
@@ -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;
}
}
-
diff --git a/app/Models/Order.php b/app/Models/Order.php
index deb31bc..f2cbfe2 100644
--- a/app/Models/Order.php
+++ b/app/Models/Order.php
@@ -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;
}
}
-
diff --git a/app/Models/Product.php b/app/Models/Product.php
index 3ebf4c7..32ffacf 100644
--- a/app/Models/Product.php
+++ b/app/Models/Product.php
@@ -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);
}
}
-
diff --git a/app/Models/User.php b/app/Models/User.php
index baec07e..4d4437d 100644
--- a/app/Models/User.php
+++ b/app/Models/User.php
@@ -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
]);
}
}
-
diff --git a/app/Views/admin/categories/form.php b/app/Views/admin/categories/form.php
index 1c74a14..f64c6c2 100644
--- a/app/Views/admin/categories/form.php
+++ b/app/Views/admin/categories/form.php
@@ -2,12 +2,12 @@
= $isEdit ? 'Редактирование категории' : 'Добавление категории' ?>
-
+
Назад к списку