Fix LESS import error and refactor project structure

This commit is contained in:
kirill.khorkov
2026-01-03 18:59:56 +03:00
parent 1bb0fc02e6
commit 4a8d4f8c3f
201 changed files with 891 additions and 14311 deletions

View File

@@ -1,122 +0,0 @@
<?php
require_once 'config/database.php';
$db = Database::getInstance()->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' создана/проверена<br>";
} catch (PDOException $e) {
echo "Ошибка создания таблицы '$table_name': " . $e->getMessage() . "<br>";
}
}
// Добавляем тестовые данные
// Проверяем, есть ли уже категории
$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 "Добавлены категории<br>";
}
// Проверяем, есть ли уже товары
$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 "Добавлены товары<br>";
}
// Проверяем, есть ли администратор
$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)<br>";
}
echo "<h3>База данных успешно инициализирована!</h3>";
echo "<a href='catalog.php'>Перейти в каталог</a>";
?>

63
.gitignore vendored
View File

@@ -1,49 +1,46 @@
# IDE и редакторы # Dependencies
/vendor/
/node_modules/
# IDE
.idea/ .idea/
.vscode/ .vscode/
*.swp *.swp
*.swo *.swo
*~ *~
# OS
.DS_Store .DS_Store
Thumbs.db
# Зависимости # Environment
/vendor/
/node_modules/
# Логи
*.log
logs/
# Загруженные файлы пользователей
/uploads/products/*
!/uploads/products/.gitkeep
# Конфигурационные файлы с секретами (если есть)
.env .env
.env.local .env.local
.env.*.local .env.*.local
# Кэш # Logs
*.log
/logs/
# Storage (uploads are gitignored, keep structure)
/storage/uploads/*
!/storage/uploads/.gitkeep
# Cache
/cache/ /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/
*.tmp *.tmp
# Скомпилированные CSS
*.css.map
# База данных SQLite (если используется локально)
*.db
*.sqlite
*.sqlite3
# Файлы резервных копий
*.bak
*.backup
# PHP debug/profiling
.phpunit.result.cache
phpunit.xml

View File

@@ -1,29 +0,0 @@
# AETERNA MVC - Корневой .htaccess
# Перенаправляет все запросы в public/
<IfModule mod_rewrite.c>
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]
</IfModule>
# Отключаем просмотр директорий
Options -Indexes

View File

@@ -1,6 +1,5 @@
FROM php:8.2-apache FROM php:8.2-apache
# Установка расширений PHP
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y \
libpq-dev \ libpq-dev \
libzip-dev \ libzip-dev \
@@ -9,26 +8,22 @@ RUN apt-get update && apt-get install -y \
&& apt-get clean \ && apt-get clean \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Включаем mod_rewrite RUN a2enmod rewrite headers expires
RUN a2enmod rewrite
# Копируем конфигурацию Apache COPY docker/apache/vhosts.conf /etc/apache2/sites-available/000-default.conf
COPY docker/apache/vhosts.conf /etc/apache2/sites-available/vhosts.conf
COPY docker/apache/entrypoint.sh /usr/local/bin/entrypoint.sh COPY docker/apache/entrypoint.sh /usr/local/bin/entrypoint.sh
RUN chmod +x /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh
# Рабочая директория
WORKDIR /var/www/html WORKDIR /var/www/html
# Копируем приложение
COPY . /var/www/html/ COPY . /var/www/html/
# Устанавливаем права RUN mkdir -p /var/www/html/storage/uploads \
RUN chown -R www-data:www-data /var/www/html && 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 EXPOSE 80
# Точка входа
ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] ENTRYPOINT ["/usr/local/bin/entrypoint.sh"]

171
README.md Normal file
View File

@@ -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 <repository-url>
cd aeterna
# Запустить контейнеры
docker-compose up -d
# Приложение будет доступно по адресу:
# http://localhost:8080
```
### Вариант 2: Локальный сервер
#### Apache
1. Настройте DocumentRoot на директорию `public/`
```apache
<VirtualHost *:80>
ServerName aeterna.local
DocumentRoot /path/to/aeterna/public
<Directory /path/to/aeterna/public>
Options -Indexes +FollowSymLinks
AllowOverride All
Require all granted
</Directory>
</VirtualHost>
```
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

View File

@@ -1,116 +0,0 @@
<?php
session_start();
require_once 'config/database.php';
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
echo json_encode(['success' => 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' => 'Неверный запрос']);
}
?>

View File

@@ -1,170 +0,0 @@
<?php
// admin_actions.php
session_start();
require_once 'config/database.php';
// Проверка прав администратора
if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
header('Location: вход.php?error=admin_only');
exit();
}
$db = Database::getInstance()->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();
?>

View File

@@ -1,772 +0,0 @@
<?php
// admin_panel.php - ПОЛНОСТЬЮ ИСПРАВЛЕННАЯ ВЕРСИЯ
session_start();
require_once 'config/database.php';
// Включаем отладку ошибок
error_reporting(E_ALL);
ini_set('display_errors', 1);
if (empty($allCategories)) {
echo '<div class="alert alert-warning">Сначала добавьте категории!</div>';
}
// Проверка прав администратора
if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
echo "<script>alert('Требуется авторизация администратора'); window.location.href = 'вход.php';</script>";
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();
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETERNA - Админ-панель</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 0; background: #f5f5f5; }
.admin-header { background: #453227; color: white; padding: 20px; display: flex; justify-content: space-between; align-items: center; }
.admin-tabs { background: white; padding: 10px; border-bottom: 2px solid #453227; display: flex; gap: 10px; }
.admin-tab { padding: 10px 20px; border-radius: 5px; text-decoration: none; color: #333; }
.admin-tab:hover, .admin-tab.active { background: #453227; color: white; }
.admin-content { padding: 20px; }
.form-container { background: white; padding: 20px; border-radius: 5px; box-shadow: 0 2px 5px rgba(0,0,0,0.1); max-width: 800px; margin: 0 auto; }
.form-group { margin-bottom: 15px; }
.form-group label { display: block; margin-bottom: 5px; font-weight: bold; }
.form-control { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
.btn { padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; text-decoration: none; display: inline-block; }
.btn-primary { background: #453227; color: white; }
.btn-success { background: #28a745; color: white; }
.btn-danger { background: #dc3545; color: white; }
.btn-warning { background: #ffc107; color: #333; }
.alert { padding: 15px; border-radius: 4px; margin-bottom: 20px; }
.alert-success { background: #d4edda; color: #155724; border: 1px solid #c3e6cb; }
.alert-danger { background: #f8d7da; color: #721c24; border: 1px solid #f5c6cb; }
table { width: 100%; border-collapse: collapse; background: white; }
th, td { padding: 10px; border: 1px solid #ddd; text-align: left; }
th { background: #f8f9fa; }
.action-buttons { display: flex; gap: 5px; }
</style>
</head>
<body>
<div class="admin-header">
<h1><i class="fas fa-user-shield"></i> Админ-панель AETERNA</h1>
<div>
<span><?= htmlspecialchars($_SESSION['user_email'] ?? 'Администратор') ?></span>
<a href="catalog.php" class="btn btn-primary" style="margin-left: 10px;">В каталог</a>
<a href="logout.php" class="btn btn-danger" style="margin-left: 10px;">Выйти</a>
</div>
</div>
<div class="admin-tabs">
<a href="?action=dashboard" class="admin-tab <?= $action == 'dashboard' ? 'active' : '' ?>">
<i class="fas fa-tachometer-alt"></i> Дашборд
</a>
<a href="?action=products" class="admin-tab <?= $action == 'products' ? 'active' : '' ?>">
<i class="fas fa-box"></i> Товары
</a>
<a href="?action=categories" class="admin-tab <?= $action == 'categories' ? 'active' : '' ?>">
<i class="fas fa-tags"></i> Категории
</a>
<a href="?action=orders" class="admin-tab <?= $action == 'orders' ? 'active' : '' ?>">
<i class="fas fa-shopping-cart"></i> Заказы
</a>
<a href="?action=users" class="admin-tab <?= $action == 'users' ? 'active' : '' ?>">
<i class="fas fa-users"></i> Пользователи
</a>
</div>
<div class="admin-content">
<?php if ($message): ?>
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> <?= htmlspecialchars(urldecode($message)) ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars(urldecode($error)) ?>
</div>
<?php endif; ?>
<?php if ($action == 'dashboard'): ?>
<!-- Дашборд -->
<h2>Статистика</h2>
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; margin: 20px 0;">
<div style="background: white; padding: 20px; border-radius: 5px; text-align: center;">
<h3><?= $stats['total_products'] ?></h3>
<p>Всего товаров</p>
</div>
<div style="background: white; padding: 20px; border-radius: 5px; text-align: center;">
<h3><?= $stats['active_products'] ?></h3>
<p>Активных товаров</p>
</div>
<div style="background: white; padding: 20px; border-radius: 5px; text-align: center;">
<h3><?= $stats['total_orders'] ?></h3>
<p>Заказов</p>
</div>
<div style="background: white; padding: 20px; border-radius: 5px; text-align: center;">
<h3><?= $stats['total_users'] ?></h3>
<p>Пользователей</p>
</div>
</div>
<div style="text-align: center; margin: 40px 0;">
<a href="?action=add_product" class="btn btn-success" style="padding: 15px 30px; font-size: 16px;">
<i class="fas fa-plus"></i> Добавить новый товар
</a>
<a href="?action=add_category" class="btn btn-primary" style="padding: 15px 30px; font-size: 16px;">
<i class="fas fa-plus"></i> Добавить категорию
</a>
</div>
<?php elseif ($action == 'products'): ?>
<!-- Товары -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление товарами</h2>
<div>
<a href="?action=add_product" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить товар
</a>
<?php if (isset($_GET['show_all'])): ?>
<a href="?action=products" class="btn btn-primary">Только активные</a>
<?php else: ?>
<a href="?action=products&show_all=1" class="btn btn-primary">Показать все</a>
<?php endif; ?>
</div>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Категория</th>
<th>Цена</th>
<th>На складе</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($data as $product): ?>
<tr>
<td><?= $product['product_id'] ?></td>
<td><?= htmlspecialchars($product['name']) ?></td>
<td><?= htmlspecialchars($product['category_name'] ?? 'Без категории') ?></td>
<td><?= number_format($product['price'], 0, '', ' ') ?> ₽</td>
<td><?= $product['stock_quantity'] ?></td>
<td>
<?php if ($product['is_available'] && $product['stock_quantity'] > 0): ?>
<span style="color: green;">✓ Доступен</span>
<?php elseif (!$product['is_available']): ?>
<span style="color: red;">✗ Недоступен</span>
<?php else: ?>
<span style="color: orange;">⚠ Нет на складе</span>
<?php endif; ?>
</td>
<td class="action-buttons">
<a href="?action=edit_product&id=<?= $product['product_id'] ?>" class="btn btn-warning btn-sm">
<i class="fas fa-edit"></i>
</a>
<?php if ($product['is_available']): ?>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="edit_product">
<input type="hidden" name="product_id" value="<?= $product['product_id'] ?>">
<input type="hidden" name="is_available" value="0">
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Сделать недоступным?')">
<i class="fas fa-times"></i>
</button>
</form>
<?php else: ?>
<form method="POST" style="display: inline;">
<input type="hidden" name="action" value="edit_product">
<input type="hidden" name="product_id" value="<?= $product['product_id'] ?>">
<input type="hidden" name="is_available" value="1">
<button type="submit" class="btn btn-success btn-sm" onclick="return confirm('Сделать доступным?')">
<i class="fas fa-check"></i>
</button>
</form>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif ($action == 'categories'): ?>
<!-- Категории -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление категориями</h2>
<a href="?action=add_category" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить категорию
</a>
</div>
<table>
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Slug</th>
<th>Родительская</th>
<th>Товаров</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($data as $category): ?>
<tr>
<td><?= $category['category_id'] ?></td>
<td><?= htmlspecialchars($category['name']) ?></td>
<td><?= htmlspecialchars($category['slug']) ?></td>
<td><?= htmlspecialchars($category['parent_name'] ?? '—') ?></td>
<td><?= $category['product_count'] ?></td>
<td class="action-buttons">
<!-- Кнопка редактирования -->
<a href="?action=edit_category&id=<?= $category['category_id'] ?>" class="btn btn-warning btn-sm">
<i class="fas fa-edit"></i> Редактировать
</a>
<!-- Кнопка удаления с AJAX -->
<button type="button" class="btn btn-danger btn-sm delete-category-btn"
data-id="<?= $category['category_id'] ?>"
<?= $category['product_count'] > 0 ? 'disabled' : '' ?>>
<i class="fas fa-trash"></i> Удалить
</button>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif (in_array($action, ['add_category', 'edit_category'])): ?>
<!-- Форма добавления/редактирования категории -->
<div class="form-container">
<h2><?= $action == 'add_category' ? 'Добавление категории' : 'Редактирование категории' ?></h2>
<form method="POST" action="fix_edit_category.php" id="categoryForm">
<input type="hidden" name="action" value="<?= $action == 'edit_category' ? 'edit_category' : 'add_category' ?>">
<?php if (isset($edit_data)): ?>
<input type="hidden" name="category_id" value="<?= $edit_data['category_id'] ?>">
<?php endif; ?>
<div class="form-group">
<label>Название категории *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($edit_data['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Родительская категория</label>
<select name="parent_id" class="form-control">
<option value="">Без родительской категории</option>
<?php foreach ($parentCategories as $cat): ?>
<?php if (!isset($edit_data['category_id']) || $cat['category_id'] != $edit_data['category_id']): ?>
<option value="<?= $cat['category_id'] ?>"
<?= (isset($edit_data['parent_id']) && $edit_data['parent_id'] == $cat['category_id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($edit_data['description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label>Порядок сортировки</label>
<input type="number" name="sort_order" class="form-control" min="0" max="100"
value="<?= $edit_data['sort_order'] ?? 0 ?>">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" value="1"
<?= (!isset($edit_data['is_active']) || $edit_data['is_active']) ? 'checked' : '' ?>>
Активна
</label>
</div>
<button type="submit" class="btn btn-primary">
<?= $action == 'add_category' ? 'Добавить категорию' : 'Сохранить изменения' ?>
</button>
<a href="?action=categories" class="btn">Отмена</a>
</form>
</div>
<?php elseif (in_array($action, ['add_category', 'edit_category'])): ?>
<!-- Форма добавления/редактирования категории -->
<div class="form-container">
<h2><?= $action == 'add_category' ? 'Добавление категории' : 'Редактирование категории' ?></h2>
<form method="POST">
<input type="hidden" name="action" value="<?= $action == 'edit_category' ? 'edit_category' : 'add_category' ?>">
<?php if (isset($edit_data)): ?>
<input type="hidden" name="category_id" value="<?= $edit_data['category_id'] ?>">
<?php endif; ?>
<div class="form-group">
<label>Название категории *</label>
<input type="text" name="name" class="form-control"
value="<?= htmlspecialchars($edit_data['name'] ?? '') ?>" required>
</div>
<div class="form-group">
<label>Родительская категория</label>
<select name="parent_id" class="form-control">
<option value="">Без родительской категории</option>
<?php foreach ($parentCategories as $cat): ?>
<?php if (!isset($edit_data['category_id']) || $cat['category_id'] != $edit_data['category_id']): ?>
<option value="<?= $cat['category_id'] ?>"
<?= (isset($edit_data['parent_id']) && $edit_data['parent_id'] == $cat['category_id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label>Описание</label>
<textarea name="description" class="form-control" rows="3"><?= htmlspecialchars($edit_data['description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label>Порядок сортировки</label>
<input type="number" name="sort_order" class="form-control" min="0" max="100"
value="<?= $edit_data['sort_order'] ?? 0 ?>">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" value="1"
<?= (!isset($edit_data['is_active']) || $edit_data['is_active']) ? 'checked' : '' ?>>
Активна
</label>
</div>
<button type="submit" class="btn btn-primary">
<?= $action == 'add_category' ? 'Добавить категорию' : 'Сохранить изменения' ?>
</button>
<a href="?action=categories" class="btn">Отмена</a>
</form>
</div>
<?php elseif ($action == 'orders'): ?>
<!-- Заказы -->
<h2>Заказы</h2>
<table>
<thead>
<tr>
<th>№ заказа</th>
<th>Клиент</th>
<th>Сумма</th>
<th>Статус</th>
<th>Дата</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($data as $order): ?>
<tr>
<td><?= htmlspecialchars($order['order_number']) ?></td>
<td><?= htmlspecialchars($order['customer_name']) ?></td>
<td><?= number_format($order['final_amount'], 0, '', ' ') ?> ₽</td>
<td><?= htmlspecialchars($order['status']) ?></td>
<td><?= date('d.m.Y H:i', strtotime($order['created_at'])) ?></td>
<td>
<a href="?action=order_details&id=<?= $order['order_id'] ?>" class="btn btn-primary btn-sm">
<i class="fas fa-eye"></i>
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php elseif ($action == 'users'): ?>
<!-- Пользователи -->
<h2>Пользователи</h2>
<table>
<thead>
<tr>
<th>ID</th>
<th>Email</th>
<th>ФИО</th>
<th>Дата регистрации</th>
<th>Статус</th>
</tr>
</thead>
<tbody>
<?php foreach ($data as $user): ?>
<tr>
<td><?= $user['user_id'] ?></td>
<td><?= htmlspecialchars($user['email']) ?></td>
<td><?= htmlspecialchars($user['full_name']) ?></td>
<td><?= date('d.m.Y', strtotime($user['created_at'])) ?></td>
<td>
<?php if ($user['is_active']): ?>
<span style="color: green;">✓ Активен</span>
<?php else: ?>
<span style="color: red;">✗ Неактивен</span>
<?php endif; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<script>
// Удаление категории через AJAX
$('.delete-category-btn').click(function() {
const categoryId = $(this).data('id');
const btn = $(this);
if (confirm('Удалить эту категорию?')) {
$.ajax({
url: 'fix_delete_category.php',
method: 'POST',
data: { category_id: categoryId },
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
alert(result.message);
location.reload();
} else {
alert('Ошибка: ' + result.message);
}
}
});
}
});
// Обработка формы категории
$('#categoryForm').submit(function(e) {
e.preventDefault();
$.ajax({
url: $(this).attr('action'),
method: 'POST',
data: $(this).serialize(),
success: function(response) {
const result = JSON.parse(response);
if (result.success) {
alert(result.message);
window.location.href = 'admin_panel.php?action=categories';
} else {
alert('Ошибка: ' + result.message);
}
}
});
});
</script>
</body>
</html>

View File

@@ -8,9 +8,6 @@ use App\Models\Category;
use App\Models\Order; use App\Models\Order;
use App\Models\User; use App\Models\User;
/**
* AdminController - контроллер админ-панели
*/
class AdminController extends Controller class AdminController extends Controller
{ {
private Product $productModel; private Product $productModel;
@@ -26,9 +23,6 @@ class AdminController extends Controller
$this->userModel = new User(); $this->userModel = new User();
} }
/**
* Дашборд
*/
public function dashboard(): void public function dashboard(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -47,11 +41,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
// ========== Товары ==========
/**
* Список товаров
*/
public function products(): void public function products(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -68,9 +57,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Форма добавления товара
*/
public function addProduct(): void public function addProduct(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -85,9 +71,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Сохранение нового товара
*/
public function storeProduct(): void public function storeProduct(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -116,9 +99,6 @@ class AdminController extends Controller
} }
} }
/**
* Форма редактирования товара
*/
public function editProduct(int $id): void public function editProduct(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -140,9 +120,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Обновление товара
*/
public function updateProduct(int $id): void public function updateProduct(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -168,9 +145,6 @@ class AdminController extends Controller
} }
} }
/**
* Удаление товара (делаем недоступным)
*/
public function deleteProduct(int $id): void public function deleteProduct(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -179,11 +153,6 @@ class AdminController extends Controller
$this->redirect('/admin/products?message=' . urlencode('Товар скрыт')); $this->redirect('/admin/products?message=' . urlencode('Товар скрыт'));
} }
// ========== Категории ==========
/**
* Список категорий
*/
public function categories(): void public function categories(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -198,9 +167,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Форма добавления категории
*/
public function addCategory(): void public function addCategory(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -215,9 +181,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Сохранение категории
*/
public function storeCategory(): void public function storeCategory(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -238,9 +201,6 @@ class AdminController extends Controller
} }
} }
/**
* Форма редактирования категории
*/
public function editCategory(int $id): void public function editCategory(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -262,9 +222,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Обновление категории
*/
public function updateCategory(int $id): void public function updateCategory(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -285,9 +242,6 @@ class AdminController extends Controller
} }
} }
/**
* Удаление категории
*/
public function deleteCategory(int $id): void public function deleteCategory(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -304,11 +258,6 @@ class AdminController extends Controller
} }
} }
// ========== Заказы ==========
/**
* Список заказов
*/
public function orders(): void public function orders(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -321,9 +270,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Детали заказа
*/
public function orderDetails(int $id): void public function orderDetails(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -341,9 +287,6 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
/**
* Обновление статуса заказа
*/
public function updateOrderStatus(int $id): void public function updateOrderStatus(int $id): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -354,11 +297,6 @@ class AdminController extends Controller
$this->redirect('/admin/orders/' . $id); $this->redirect('/admin/orders/' . $id);
} }
// ========== Пользователи ==========
/**
* Список пользователей
*/
public function users(): void public function users(): void
{ {
$this->requireAdmin(); $this->requireAdmin();
@@ -371,4 +309,3 @@ class AdminController extends Controller
], 'admin'); ], 'admin');
} }
} }

View File

@@ -5,9 +5,6 @@ namespace App\Controllers;
use App\Core\Controller; use App\Core\Controller;
use App\Models\User; use App\Models\User;
/**
* AuthController - контроллер авторизации
*/
class AuthController extends Controller class AuthController extends Controller
{ {
private User $userModel; private User $userModel;
@@ -17,9 +14,6 @@ class AuthController extends Controller
$this->userModel = new User(); $this->userModel = new User();
} }
/**
* Форма входа
*/
public function loginForm(): void public function loginForm(): void
{ {
if ($this->isAuthenticated()) { if ($this->isAuthenticated()) {
@@ -35,9 +29,6 @@ class AuthController extends Controller
]); ]);
} }
/**
* Обработка входа
*/
public function login(): void public function login(): void
{ {
$email = $this->getPost('email', ''); $email = $this->getPost('email', '');
@@ -62,7 +53,6 @@ class AuthController extends Controller
return; return;
} }
// Устанавливаем сессию
$this->setSession($user); $this->setSession($user);
$this->json([ $this->json([
@@ -71,9 +61,6 @@ class AuthController extends Controller
]); ]);
} }
/**
* Форма регистрации
*/
public function registerForm(): void public function registerForm(): void
{ {
if ($this->isAuthenticated()) { if ($this->isAuthenticated()) {
@@ -86,15 +73,11 @@ class AuthController extends Controller
'success' => $_SESSION['registration_success'] ?? null 'success' => $_SESSION['registration_success'] ?? null
]); ]);
// Очищаем flash данные
unset($_SESSION['registration_errors']); unset($_SESSION['registration_errors']);
unset($_SESSION['old_data']); unset($_SESSION['old_data']);
unset($_SESSION['registration_success']); unset($_SESSION['registration_success']);
} }
/**
* Обработка регистрации
*/
public function register(): void public function register(): void
{ {
$errors = []; $errors = [];
@@ -107,7 +90,6 @@ class AuthController extends Controller
$confirmPassword = $this->getPost('confirm-password', ''); $confirmPassword = $this->getPost('confirm-password', '');
$privacy = $this->getPost('privacy'); $privacy = $this->getPost('privacy');
// Валидация
if (empty($fullName) || strlen($fullName) < 3) { if (empty($fullName) || strlen($fullName) < 3) {
$errors[] = 'ФИО должно содержать минимум 3 символа'; $errors[] = 'ФИО должно содержать минимум 3 символа';
} }
@@ -136,7 +118,6 @@ class AuthController extends Controller
$errors[] = 'Необходимо согласие с условиями обработки персональных данных'; $errors[] = 'Необходимо согласие с условиями обработки персональных данных';
} }
// Проверяем существование email
if (empty($errors) && $this->userModel->emailExists($email)) { if (empty($errors) && $this->userModel->emailExists($email)) {
$errors[] = 'Пользователь с таким email уже существует'; $errors[] = 'Пользователь с таким email уже существует';
} }
@@ -153,7 +134,6 @@ class AuthController extends Controller
return; return;
} }
// Создаем пользователя
try { try {
$userId = $this->userModel->register([ $userId = $this->userModel->register([
'email' => $email, 'email' => $email,
@@ -167,10 +147,7 @@ class AuthController extends Controller
throw new \Exception('Ошибка при создании пользователя'); throw new \Exception('Ошибка при создании пользователя');
} }
// Получаем созданного пользователя
$user = $this->userModel->find($userId); $user = $this->userModel->find($userId);
// Устанавливаем сессию
$this->setSession($user); $this->setSession($user);
$_SESSION['registration_success'] = 'Регистрация прошла успешно!'; $_SESSION['registration_success'] = 'Регистрация прошла успешно!';
@@ -188,9 +165,6 @@ class AuthController extends Controller
} }
} }
/**
* Выход из системы
*/
public function logout(): void public function logout(): void
{ {
session_destroy(); session_destroy();
@@ -199,9 +173,6 @@ class AuthController extends Controller
$this->redirect('/'); $this->redirect('/');
} }
/**
* Установить сессию пользователя
*/
private function setSession(array $user): void private function setSession(array $user): void
{ {
$_SESSION['user_id'] = $user['user_id']; $_SESSION['user_id'] = $user['user_id'];
@@ -214,4 +185,3 @@ class AuthController extends Controller
$_SESSION['login_time'] = time(); $_SESSION['login_time'] = time();
} }
} }

View File

@@ -6,9 +6,6 @@ use App\Core\Controller;
use App\Models\Cart; use App\Models\Cart;
use App\Models\Product; use App\Models\Product;
/**
* CartController - контроллер корзины
*/
class CartController extends Controller class CartController extends Controller
{ {
private Cart $cartModel; private Cart $cartModel;
@@ -20,9 +17,6 @@ class CartController extends Controller
$this->productModel = new Product(); $this->productModel = new Product();
} }
/**
* Страница корзины
*/
public function index(): void public function index(): void
{ {
$this->requireAuth(); $this->requireAuth();
@@ -39,9 +33,6 @@ class CartController extends Controller
]); ]);
} }
/**
* Добавить товар в корзину
*/
public function add(): void public function add(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -64,7 +55,6 @@ class CartController extends Controller
return; return;
} }
// Проверяем наличие товара
$product = $this->productModel->find($productId); $product = $this->productModel->find($productId);
if (!$product || !$product['is_available']) { if (!$product || !$product['is_available']) {
@@ -75,7 +65,6 @@ class CartController extends Controller
return; return;
} }
// Проверяем количество на складе
$cartItem = $this->cartModel->getItem($userId, $productId); $cartItem = $this->cartModel->getItem($userId, $productId);
$currentQty = $cartItem ? $cartItem['quantity'] : 0; $currentQty = $cartItem ? $cartItem['quantity'] : 0;
$newQty = $currentQty + $quantity; $newQty = $currentQty + $quantity;
@@ -88,7 +77,6 @@ class CartController extends Controller
return; return;
} }
// Добавляем в корзину
$result = $this->cartModel->addItem($userId, $productId, $quantity); $result = $this->cartModel->addItem($userId, $productId, $quantity);
if ($result) { if ($result) {
@@ -106,9 +94,6 @@ class CartController extends Controller
} }
} }
/**
* Обновить количество товара
*/
public function update(): void public function update(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -124,7 +109,6 @@ class CartController extends Controller
$userId = $this->getCurrentUser()['id']; $userId = $this->getCurrentUser()['id'];
if ($quantity <= 0) { if ($quantity <= 0) {
// Если количество 0 или меньше - удаляем
$this->cartModel->removeItem($userId, $productId); $this->cartModel->removeItem($userId, $productId);
$cartCount = $this->cartModel->getCount($userId); $cartCount = $this->cartModel->getCount($userId);
$this->json([ $this->json([
@@ -134,7 +118,6 @@ class CartController extends Controller
return; return;
} }
// Проверяем наличие на складе
$product = $this->productModel->find($productId); $product = $this->productModel->find($productId);
if (!$product || $quantity > $product['stock_quantity']) { if (!$product || $quantity > $product['stock_quantity']) {
$this->json([ $this->json([
@@ -161,9 +144,6 @@ class CartController extends Controller
} }
} }
/**
* Удалить товар из корзины
*/
public function remove(): void public function remove(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -193,9 +173,6 @@ class CartController extends Controller
} }
} }
/**
* Получить количество товаров в корзине
*/
public function count(): void public function count(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -215,4 +192,3 @@ class CartController extends Controller
]); ]);
} }
} }

View File

@@ -4,14 +4,8 @@ namespace App\Controllers;
use App\Core\Controller; use App\Core\Controller;
/**
* HomeController - контроллер главной страницы
*/
class HomeController extends Controller class HomeController extends Controller
{ {
/**
* Главная страница
*/
public function index(): void public function index(): void
{ {
$user = $this->getCurrentUser(); $user = $this->getCurrentUser();
@@ -23,4 +17,3 @@ class HomeController extends Controller
]); ]);
} }
} }

View File

@@ -6,9 +6,6 @@ use App\Core\Controller;
use App\Models\Order; use App\Models\Order;
use App\Models\Cart; use App\Models\Cart;
/**
* OrderController - контроллер заказов
*/
class OrderController extends Controller class OrderController extends Controller
{ {
private Order $orderModel; private Order $orderModel;
@@ -20,9 +17,6 @@ class OrderController extends Controller
$this->cartModel = new Cart(); $this->cartModel = new Cart();
} }
/**
* Страница оформления заказа (корзина)
*/
public function checkout(): void public function checkout(): void
{ {
$this->requireAuth(); $this->requireAuth();
@@ -39,9 +33,6 @@ class OrderController extends Controller
]); ]);
} }
/**
* Создание заказа
*/
public function create(): void public function create(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -63,7 +54,6 @@ class OrderController extends Controller
return; return;
} }
// Получаем данные заказа
$orderData = [ $orderData = [
'customer_name' => $this->getPost('full_name', $user['full_name']), 'customer_name' => $this->getPost('full_name', $user['full_name']),
'customer_email' => $this->getPost('email', $user['email']), 'customer_email' => $this->getPost('email', $user['email']),
@@ -79,7 +69,6 @@ class OrderController extends Controller
'notes' => $this->getPost('notes', '') 'notes' => $this->getPost('notes', '')
]; ];
// Валидация
if (empty($orderData['customer_name'])) { if (empty($orderData['customer_name'])) {
$this->json([ $this->json([
'success' => false, 'success' => false,
@@ -122,4 +111,3 @@ class OrderController extends Controller
} }
} }
} }

View File

@@ -4,14 +4,8 @@ namespace App\Controllers;
use App\Core\Controller; use App\Core\Controller;
/**
* PageController - контроллер статических страниц
*/
class PageController extends Controller class PageController extends Controller
{ {
/**
* Страница услуг
*/
public function services(): void public function services(): void
{ {
$this->view('pages/services', [ $this->view('pages/services', [
@@ -20,9 +14,6 @@ class PageController extends Controller
]); ]);
} }
/**
* Страница доставки и оплаты
*/
public function delivery(): void public function delivery(): void
{ {
$this->view('pages/delivery', [ $this->view('pages/delivery', [
@@ -31,9 +22,6 @@ class PageController extends Controller
]); ]);
} }
/**
* Страница гарантии
*/
public function warranty(): void public function warranty(): void
{ {
$this->view('pages/warranty', [ $this->view('pages/warranty', [
@@ -42,4 +30,3 @@ class PageController extends Controller
]); ]);
} }
} }

View File

@@ -6,9 +6,6 @@ use App\Core\Controller;
use App\Models\Product; use App\Models\Product;
use App\Models\Category; use App\Models\Category;
/**
* ProductController - контроллер товаров и каталога
*/
class ProductController extends Controller class ProductController extends Controller
{ {
private Product $productModel; private Product $productModel;
@@ -20,9 +17,6 @@ class ProductController extends Controller
$this->categoryModel = new Category(); $this->categoryModel = new Category();
} }
/**
* Каталог товаров
*/
public function catalog(): void public function catalog(): void
{ {
$this->requireAuth(); $this->requireAuth();
@@ -30,7 +24,6 @@ class ProductController extends Controller
$user = $this->getCurrentUser(); $user = $this->getCurrentUser();
$isAdmin = $this->isAdmin(); $isAdmin = $this->isAdmin();
// Получаем параметры фильтрации
$filters = [ $filters = [
'category_id' => (int) $this->getQuery('category', 0), 'category_id' => (int) $this->getQuery('category', 0),
'search' => $this->getQuery('search', ''), 'search' => $this->getQuery('search', ''),
@@ -42,7 +35,6 @@ class ProductController extends Controller
$showAll = $isAdmin && $this->getQuery('show_all') === '1'; $showAll = $isAdmin && $this->getQuery('show_all') === '1';
// Получаем данные
$categories = $this->categoryModel->getActive(); $categories = $this->categoryModel->getActive();
$products = $showAll $products = $showAll
? $this->productModel->getAllForAdmin(true) ? $this->productModel->getAllForAdmin(true)
@@ -51,7 +43,6 @@ class ProductController extends Controller
$availableColors = $this->productModel->getAvailableColors(); $availableColors = $this->productModel->getAvailableColors();
$availableMaterials = $this->productModel->getAvailableMaterials(); $availableMaterials = $this->productModel->getAvailableMaterials();
// Подкатегории для выбранной категории
$subcategories = []; $subcategories = [];
if ($filters['category_id'] > 0) { if ($filters['category_id'] > 0) {
$subcategories = $this->categoryModel->getChildren($filters['category_id']); $subcategories = $this->categoryModel->getChildren($filters['category_id']);
@@ -72,9 +63,6 @@ class ProductController extends Controller
]); ]);
} }
/**
* Страница товара
*/
public function show(int $id): void public function show(int $id): void
{ {
$this->requireAuth(); $this->requireAuth();
@@ -99,4 +87,3 @@ class ProductController extends Controller
]); ]);
} }
} }

View File

@@ -2,66 +2,68 @@
namespace App\Core; namespace App\Core;
/**
* App - главный класс приложения
*/
class App class App
{ {
private Router $router; private Router $router;
private static ?App $instance = null; private static ?App $instance = null;
private array $config = [];
public function __construct() public function __construct()
{ {
self::$instance = $this; self::$instance = $this;
// Регистрируем автозагрузчик сразу
$this->registerAutoloader(); $this->registerAutoloader();
$this->router = new Router(); $this->router = new Router();
} }
/**
* Получить экземпляр приложения
*/
public static function getInstance(): ?self public static function getInstance(): ?self
{ {
return self::$instance; return self::$instance;
} }
/**
* Получить роутер
*/
public function getRouter(): Router public function getRouter(): Router
{ {
return $this->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 public function init(): self
{ {
// Запускаем сессию $this->loadConfig();
date_default_timezone_set($this->config['timezone'] ?? 'Europe/Moscow');
if (session_status() === PHP_SESSION_NONE) { if (session_status() === PHP_SESSION_NONE) {
session_start(); session_start();
} }
// Настраиваем обработку ошибок
$this->setupErrorHandling(); $this->setupErrorHandling();
// Загружаем маршруты
$this->loadRoutes(); $this->loadRoutes();
return $this; 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 private function registerAutoloader(): void
{ {
spl_autoload_register(function ($class) { spl_autoload_register(function ($class) {
// Преобразуем namespace в путь к файлу
$prefix = 'App\\'; $prefix = 'App\\';
$baseDir = dirname(__DIR__) . '/'; $baseDir = dirname(__DIR__) . '/';
@@ -79,14 +81,9 @@ class App
}); });
} }
/**
* Настройка обработки ошибок
*/
private function setupErrorHandling(): void private function setupErrorHandling(): void
{ {
$config = require dirname(__DIR__, 2) . '/config/app.php'; if ($this->config['debug'] ?? false) {
if ($config['debug'] ?? false) {
error_reporting(E_ALL); error_reporting(E_ALL);
ini_set('display_errors', '1'); ini_set('display_errors', '1');
} else { } else {
@@ -103,16 +100,11 @@ class App
}); });
} }
/**
* Обработка исключений
*/
private function handleException(\Throwable $e): void private function handleException(\Throwable $e): void
{ {
$config = require dirname(__DIR__, 2) . '/config/app.php';
http_response_code(500); http_response_code(500);
if ($config['debug'] ?? false) { if ($this->config['debug'] ?? false) {
echo "<h1>Ошибка приложения</h1>"; echo "<h1>Ошибка приложения</h1>";
echo "<p><strong>Сообщение:</strong> " . htmlspecialchars($e->getMessage()) . "</p>"; echo "<p><strong>Сообщение:</strong> " . htmlspecialchars($e->getMessage()) . "</p>";
echo "<p><strong>Файл:</strong> " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "</p>"; echo "<p><strong>Файл:</strong> " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "</p>";
@@ -122,12 +114,9 @@ class App
} }
} }
/**
* Загрузка маршрутов
*/
private function loadRoutes(): void private function loadRoutes(): void
{ {
$routesFile = dirname(__DIR__, 2) . '/config/routes.php'; $routesFile = $this->getBasePath() . '/config/routes.php';
if (file_exists($routesFile)) { if (file_exists($routesFile)) {
$router = $this->router; $router = $this->router;
@@ -135,21 +124,17 @@ class App
} }
} }
/**
* Запуск приложения
*/
public function run(): void public function run(): void
{ {
$uri = $_SERVER['REQUEST_URI'] ?? '/'; $uri = $_SERVER['REQUEST_URI'] ?? '/';
$method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; $method = $_SERVER['REQUEST_METHOD'] ?? 'GET';
// Удаляем базовый путь, если он есть $basePath = $this->config['base_path'] ?? '';
$basePath = '/cite_practica';
if (strpos($uri, $basePath) === 0) { if (!empty($basePath) && strpos($uri, $basePath) === 0) {
$uri = substr($uri, strlen($basePath)); $uri = substr($uri, strlen($basePath));
} }
// Если URI пустой, делаем его корневым
if (empty($uri) || $uri === false) { if (empty($uri) || $uri === false) {
$uri = '/'; $uri = '/';
} }
@@ -161,4 +146,3 @@ class App
} }
} }
} }

View File

@@ -2,35 +2,20 @@
namespace App\Core; namespace App\Core;
/**
* Controller - базовый класс контроллера
*/
abstract class Controller abstract class Controller
{ {
/**
* Данные для передачи в представление
*/
protected array $data = []; protected array $data = [];
/**
* Отрендерить представление
*/
protected function view(string $view, array $data = [], string $layout = 'main'): void protected function view(string $view, array $data = [], string $layout = 'main'): void
{ {
echo View::render($view, $data, $layout); echo View::render($view, $data, $layout);
} }
/**
* Отрендерить представление без layout
*/
protected function viewPartial(string $view, array $data = []): void protected function viewPartial(string $view, array $data = []): void
{ {
echo View::render($view, $data, null); echo View::render($view, $data, null);
} }
/**
* Вернуть JSON ответ
*/
protected function json(array $data, int $statusCode = 200): void protected function json(array $data, int $statusCode = 200): void
{ {
http_response_code($statusCode); http_response_code($statusCode);
@@ -39,18 +24,12 @@ abstract class Controller
exit; exit;
} }
/**
* Редирект на другой URL
*/
protected function redirect(string $url): void protected function redirect(string $url): void
{ {
header("Location: {$url}"); header("Location: {$url}");
exit; exit;
} }
/**
* Получить текущего пользователя из сессии
*/
protected function getCurrentUser(): ?array protected function getCurrentUser(): ?array
{ {
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) { if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
@@ -68,25 +47,16 @@ abstract class Controller
]; ];
} }
/**
* Проверить, авторизован ли пользователь
*/
protected function isAuthenticated(): bool protected function isAuthenticated(): bool
{ {
return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true; return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true;
} }
/**
* Проверить, является ли пользователь администратором
*/
protected function isAdmin(): bool protected function isAdmin(): bool
{ {
return $this->isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; return $this->isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
} }
/**
* Требовать авторизацию
*/
protected function requireAuth(): void protected function requireAuth(): void
{ {
if (!$this->isAuthenticated()) { if (!$this->isAuthenticated()) {
@@ -95,9 +65,6 @@ abstract class Controller
} }
} }
/**
* Требовать права администратора
*/
protected function requireAdmin(): void protected function requireAdmin(): void
{ {
if (!$this->isAdmin()) { if (!$this->isAdmin()) {
@@ -105,9 +72,6 @@ abstract class Controller
} }
} }
/**
* Получить POST данные
*/
protected function getPost(?string $key = null, $default = null) protected function getPost(?string $key = null, $default = null)
{ {
if ($key === null) { if ($key === null) {
@@ -116,9 +80,6 @@ abstract class Controller
return $_POST[$key] ?? $default; return $_POST[$key] ?? $default;
} }
/**
* Получить GET данные
*/
protected function getQuery(?string $key = null, $default = null) protected function getQuery(?string $key = null, $default = null)
{ {
if ($key === null) { if ($key === null) {
@@ -127,17 +88,11 @@ abstract class Controller
return $_GET[$key] ?? $default; return $_GET[$key] ?? $default;
} }
/**
* Установить flash-сообщение
*/
protected function setFlash(string $type, string $message): void protected function setFlash(string $type, string $message): void
{ {
$_SESSION['flash'][$type] = $message; $_SESSION['flash'][$type] = $message;
} }
/**
* Получить flash-сообщение
*/
protected function getFlash(string $type): ?string protected function getFlash(string $type): ?string
{ {
$message = $_SESSION['flash'][$type] ?? null; $message = $_SESSION['flash'][$type] ?? null;
@@ -145,4 +100,3 @@ abstract class Controller
return $message; return $message;
} }
} }

View File

@@ -2,9 +2,6 @@
namespace App\Core; namespace App\Core;
/**
* Database - Singleton класс для подключения к PostgreSQL
*/
class Database class Database
{ {
private static ?Database $instance = null; private static ?Database $instance = null;
@@ -38,9 +35,6 @@ class Database
return $this->connection; return $this->connection;
} }
/**
* Выполнить SELECT запрос
*/
public function query(string $sql, array $params = []): array public function query(string $sql, array $params = []): array
{ {
$stmt = $this->connection->prepare($sql); $stmt = $this->connection->prepare($sql);
@@ -48,9 +42,6 @@ class Database
return $stmt->fetchAll(); return $stmt->fetchAll();
} }
/**
* Выполнить SELECT запрос и получить одну запись
*/
public function queryOne(string $sql, array $params = []): ?array public function queryOne(string $sql, array $params = []): ?array
{ {
$stmt = $this->connection->prepare($sql); $stmt = $this->connection->prepare($sql);
@@ -59,52 +50,36 @@ class Database
return $result ?: null; return $result ?: null;
} }
/**
* Выполнить INSERT/UPDATE/DELETE запрос
*/
public function execute(string $sql, array $params = []): bool public function execute(string $sql, array $params = []): bool
{ {
$stmt = $this->connection->prepare($sql); $stmt = $this->connection->prepare($sql);
return $stmt->execute($params); return $stmt->execute($params);
} }
/**
* Получить ID последней вставленной записи
*/
public function lastInsertId(): string public function lastInsertId(): string
{ {
return $this->connection->lastInsertId(); return $this->connection->lastInsertId();
} }
/**
* Начать транзакцию
*/
public function beginTransaction(): bool public function beginTransaction(): bool
{ {
return $this->connection->beginTransaction(); return $this->connection->beginTransaction();
} }
/**
* Подтвердить транзакцию
*/
public function commit(): bool public function commit(): bool
{ {
return $this->connection->commit(); return $this->connection->commit();
} }
/**
* Откатить транзакцию
*/
public function rollBack(): bool public function rollBack(): bool
{ {
return $this->connection->rollBack(); return $this->connection->rollBack();
} }
// Запрещаем клонирование и десериализацию
private function __clone() {} private function __clone() {}
public function __wakeup() public function __wakeup()
{ {
throw new \Exception("Десериализация Singleton запрещена"); throw new \Exception("Десериализация Singleton запрещена");
} }
} }

View File

@@ -2,9 +2,6 @@
namespace App\Core; namespace App\Core;
/**
* Model - базовый класс модели
*/
abstract class Model abstract class Model
{ {
protected Database $db; protected Database $db;
@@ -16,18 +13,12 @@ abstract class Model
$this->db = Database::getInstance(); $this->db = Database::getInstance();
} }
/**
* Найти запись по первичному ключу
*/
public function find(int $id): ?array public function find(int $id): ?array
{ {
$sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?"; $sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?";
return $this->db->queryOne($sql, [$id]); return $this->db->queryOne($sql, [$id]);
} }
/**
* Получить все записи
*/
public function all(?string $orderBy = null): array public function all(?string $orderBy = null): array
{ {
$sql = "SELECT * FROM {$this->table}"; $sql = "SELECT * FROM {$this->table}";
@@ -37,9 +28,6 @@ abstract class Model
return $this->db->query($sql); return $this->db->query($sql);
} }
/**
* Найти записи по условию
*/
public function where(array $conditions, ?string $orderBy = null): array public function where(array $conditions, ?string $orderBy = null): array
{ {
$where = []; $where = [];
@@ -59,9 +47,6 @@ abstract class Model
return $this->db->query($sql, $params); return $this->db->query($sql, $params);
} }
/**
* Найти одну запись по условию
*/
public function findWhere(array $conditions): ?array public function findWhere(array $conditions): ?array
{ {
$where = []; $where = [];
@@ -76,9 +61,6 @@ abstract class Model
return $this->db->queryOne($sql, $params); return $this->db->queryOne($sql, $params);
} }
/**
* Создать новую запись
*/
public function create(array $data): ?int public function create(array $data): ?int
{ {
$columns = array_keys($data); $columns = array_keys($data);
@@ -98,9 +80,6 @@ abstract class Model
return (int) $stmt->fetchColumn(); return (int) $stmt->fetchColumn();
} }
/**
* Обновить запись
*/
public function update(int $id, array $data): bool public function update(int $id, array $data): bool
{ {
$set = []; $set = [];
@@ -122,18 +101,12 @@ abstract class Model
return $this->db->execute($sql, $params); return $this->db->execute($sql, $params);
} }
/**
* Удалить запись
*/
public function delete(int $id): bool public function delete(int $id): bool
{ {
$sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?"; $sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?";
return $this->db->execute($sql, [$id]); return $this->db->execute($sql, [$id]);
} }
/**
* Подсчитать количество записей
*/
public function count(array $conditions = []): int public function count(array $conditions = []): int
{ {
$sql = "SELECT COUNT(*) FROM {$this->table}"; $sql = "SELECT COUNT(*) FROM {$this->table}";
@@ -153,28 +126,18 @@ abstract class Model
return (int) $stmt->fetchColumn(); return (int) $stmt->fetchColumn();
} }
/**
* Выполнить произвольный SQL запрос
*/
protected function query(string $sql, array $params = []): array protected function query(string $sql, array $params = []): array
{ {
return $this->db->query($sql, $params); return $this->db->query($sql, $params);
} }
/**
* Выполнить произвольный SQL запрос и получить одну запись
*/
protected function queryOne(string $sql, array $params = []): ?array protected function queryOne(string $sql, array $params = []): ?array
{ {
return $this->db->queryOne($sql, $params); return $this->db->queryOne($sql, $params);
} }
/**
* Выполнить произвольный SQL запрос (INSERT/UPDATE/DELETE)
*/
protected function execute(string $sql, array $params = []): bool protected function execute(string $sql, array $params = []): bool
{ {
return $this->db->execute($sql, $params); return $this->db->execute($sql, $params);
} }
} }

View File

@@ -2,17 +2,11 @@
namespace App\Core; namespace App\Core;
/**
* Router - маршрутизатор запросов
*/
class Router class Router
{ {
private array $routes = []; private array $routes = [];
private array $params = []; private array $params = [];
/**
* Добавить маршрут
*/
public function add(string $method, string $route, string $controller, string $action): self public function add(string $method, string $route, string $controller, string $action): self
{ {
$this->routes[] = [ $this->routes[] = [
@@ -24,25 +18,16 @@ class Router
return $this; return $this;
} }
/**
* GET маршрут
*/
public function get(string $route, string $controller, string $action): self public function get(string $route, string $controller, string $action): self
{ {
return $this->add('GET', $route, $controller, $action); return $this->add('GET', $route, $controller, $action);
} }
/**
* POST маршрут
*/
public function post(string $route, string $controller, string $action): self public function post(string $route, string $controller, string $action): self
{ {
return $this->add('POST', $route, $controller, $action); return $this->add('POST', $route, $controller, $action);
} }
/**
* Найти маршрут по URL и методу
*/
public function match(string $url, string $method): ?array public function match(string $url, string $method): ?array
{ {
$method = strtoupper($method); $method = strtoupper($method);
@@ -57,7 +42,6 @@ class Router
$pattern = $this->convertRouteToRegex($route['route']); $pattern = $this->convertRouteToRegex($route['route']);
if (preg_match($pattern, $url, $matches)) { if (preg_match($pattern, $url, $matches)) {
// Извлекаем параметры из URL
$this->params = $this->extractParams($route['route'], $matches); $this->params = $this->extractParams($route['route'], $matches);
return [ return [
@@ -71,27 +55,16 @@ class Router
return null; return null;
} }
/**
* Преобразовать маршрут в регулярное выражение
*/
private function convertRouteToRegex(string $route): string private function convertRouteToRegex(string $route): string
{ {
$route = trim($route, '/'); $route = trim($route, '/');
// Заменяем {param} на regex группу
$pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '([^/]+)', $route); $pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '([^/]+)', $route);
return '#^' . $pattern . '$#'; return '#^' . $pattern . '$#';
} }
/**
* Извлечь параметры из совпадений
*/
private function extractParams(string $route, array $matches): array private function extractParams(string $route, array $matches): array
{ {
$params = []; $params = [];
// Находим все {param} в маршруте
preg_match_all('/\{([a-zA-Z_]+)\}/', $route, $paramNames); preg_match_all('/\{([a-zA-Z_]+)\}/', $route, $paramNames);
foreach ($paramNames[1] as $index => $name) { foreach ($paramNames[1] as $index => $name) {
@@ -103,9 +76,6 @@ class Router
return $params; return $params;
} }
/**
* Удалить query string из URL
*/
private function removeQueryString(string $url): string private function removeQueryString(string $url): string
{ {
if ($pos = strpos($url, '?')) { if ($pos = strpos($url, '?')) {
@@ -114,17 +84,11 @@ class Router
return $url; return $url;
} }
/**
* Получить параметры маршрута
*/
public function getParams(): array public function getParams(): array
{ {
return $this->params; return $this->params;
} }
/**
* Диспетчеризация запроса
*/
public function dispatch(string $url, string $method): void public function dispatch(string $url, string $method): void
{ {
$match = $this->match($url, $method); $match = $this->match($url, $method);
@@ -148,8 +112,6 @@ class Router
throw new \Exception("Метод {$action} не найден в контроллере {$controllerClass}"); throw new \Exception("Метод {$action} не найден в контроллере {$controllerClass}");
} }
// Вызываем метод контроллера с параметрами
call_user_func_array([$controller, $action], $match['params']); call_user_func_array([$controller, $action], $match['params']);
} }
} }

View File

@@ -2,24 +2,15 @@
namespace App\Core; namespace App\Core;
/**
* View - класс для рендеринга представлений
*/
class View class View
{ {
private static string $viewsPath = ''; private static string $viewsPath = '';
/**
* Установить путь к директории представлений
*/
public static function setViewsPath(string $path): void public static function setViewsPath(string $path): void
{ {
self::$viewsPath = rtrim($path, '/'); self::$viewsPath = rtrim($path, '/');
} }
/**
* Получить путь к директории представлений
*/
public static function getViewsPath(): string public static function getViewsPath(): string
{ {
if (empty(self::$viewsPath)) { if (empty(self::$viewsPath)) {
@@ -28,9 +19,6 @@ class View
return self::$viewsPath; return self::$viewsPath;
} }
/**
* Отрендерить представление
*/
public static function render(string $view, array $data = [], ?string $layout = 'main'): string public static function render(string $view, array $data = [], ?string $layout = 'main'): string
{ {
$viewPath = self::getViewsPath() . '/' . str_replace('.', '/', $view) . '.php'; $viewPath = self::getViewsPath() . '/' . str_replace('.', '/', $view) . '.php';
@@ -39,15 +27,12 @@ class View
throw new \Exception("Представление не найдено: {$viewPath}"); throw new \Exception("Представление не найдено: {$viewPath}");
} }
// Извлекаем данные в переменные
extract($data); extract($data);
// Буферизируем вывод контента
ob_start(); ob_start();
require $viewPath; require $viewPath;
$content = ob_get_clean(); $content = ob_get_clean();
// Если есть layout, оборачиваем контент
if ($layout !== null) { if ($layout !== null) {
$layoutPath = self::getViewsPath() . '/layouts/' . $layout . '.php'; $layoutPath = self::getViewsPath() . '/layouts/' . $layout . '.php';
@@ -63,9 +48,6 @@ class View
return $content; return $content;
} }
/**
* Отрендерить partial (часть шаблона)
*/
public static function partial(string $partial, array $data = []): string public static function partial(string $partial, array $data = []): string
{ {
$partialPath = self::getViewsPath() . '/partials/' . $partial . '.php'; $partialPath = self::getViewsPath() . '/partials/' . $partial . '.php';
@@ -81,49 +63,31 @@ class View
return ob_get_clean(); return ob_get_clean();
} }
/**
* Экранирование HTML
*/
public static function escape(string $value): string public static function escape(string $value): string
{ {
return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); return htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
} }
/**
* Сокращенный алиас для escape
*/
public static function e(string $value): string public static function e(string $value): string
{ {
return self::escape($value); return self::escape($value);
} }
/**
* Форматирование цены
*/
public static function formatPrice($price): string public static function formatPrice($price): string
{ {
return number_format((float)$price, 0, '', ' ') . ' ₽'; return number_format((float)$price, 0, '', ' ') . ' ₽';
} }
/**
* Форматирование даты
*/
public static function formatDate(string $date, string $format = 'd.m.Y'): string public static function formatDate(string $date, string $format = 'd.m.Y'): string
{ {
return date($format, strtotime($date)); return date($format, strtotime($date));
} }
/**
* Форматирование даты и времени
*/
public static function formatDateTime(string $date, string $format = 'd.m.Y H:i'): string public static function formatDateTime(string $date, string $format = 'd.m.Y H:i'): string
{ {
return date($format, strtotime($date)); return date($format, strtotime($date));
} }
/**
* Получить flash-сообщения
*/
public static function getFlashMessages(): array public static function getFlashMessages(): array
{ {
$messages = $_SESSION['flash'] ?? []; $messages = $_SESSION['flash'] ?? [];
@@ -131,25 +95,16 @@ class View
return $messages; return $messages;
} }
/**
* Проверить, авторизован ли пользователь
*/
public static function isAuthenticated(): bool public static function isAuthenticated(): bool
{ {
return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true; return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true;
} }
/**
* Проверить, является ли пользователь администратором
*/
public static function isAdmin(): bool public static function isAdmin(): bool
{ {
return self::isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; return self::isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
} }
/**
* Получить данные текущего пользователя
*/
public static function currentUser(): ?array public static function currentUser(): ?array
{ {
if (!self::isAuthenticated()) { if (!self::isAuthenticated()) {
@@ -165,20 +120,13 @@ class View
]; ];
} }
/**
* Генерация URL
*/
public static function url(string $path): string public static function url(string $path): string
{ {
return '/' . ltrim($path, '/'); return '/' . ltrim($path, '/');
} }
/**
* Генерация URL для ассетов
*/
public static function asset(string $path): string public static function asset(string $path): string
{ {
return '/assets/' . ltrim($path, '/'); return '/assets/' . ltrim($path, '/');
} }
} }

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model; use App\Core\Model;
/**
* Cart - модель корзины
*/
class Cart extends Model class Cart extends Model
{ {
protected string $table = 'cart'; protected string $table = 'cart';
protected string $primaryKey = 'cart_id'; protected string $primaryKey = 'cart_id';
/**
* Получить корзину пользователя
*/
public function getUserCart(int $userId): array public function getUserCart(int $userId): array
{ {
$sql = "SELECT $sql = "SELECT
@@ -33,9 +27,6 @@ class Cart extends Model
return $this->query($sql, [$userId]); return $this->query($sql, [$userId]);
} }
/**
* Получить общую сумму корзины
*/
public function getCartTotal(int $userId): array public function getCartTotal(int $userId): array
{ {
$sql = "SELECT $sql = "SELECT
@@ -52,9 +43,6 @@ class Cart extends Model
]; ];
} }
/**
* Получить количество товаров в корзине
*/
public function getCount(int $userId): int public function getCount(int $userId): int
{ {
$sql = "SELECT COALESCE(SUM(quantity), 0) as total FROM {$this->table} WHERE user_id = ?"; $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']; return (int) $result['total'];
} }
/**
* Добавить товар в корзину
*/
public function addItem(int $userId, int $productId, int $quantity = 1): bool public function addItem(int $userId, int $productId, int $quantity = 1): bool
{ {
// Проверяем, есть ли уже этот товар в корзине
$existing = $this->findWhere([ $existing = $this->findWhere([
'user_id' => $userId, 'user_id' => $userId,
'product_id' => $productId 'product_id' => $productId
]); ]);
if ($existing) { if ($existing) {
// Увеличиваем количество
$newQuantity = $existing['quantity'] + $quantity; $newQuantity = $existing['quantity'] + $quantity;
return $this->update($existing['cart_id'], [ return $this->update($existing['cart_id'], [
'quantity' => $newQuantity, 'quantity' => $newQuantity,
@@ -82,7 +65,6 @@ class Cart extends Model
]); ]);
} }
// Добавляем новую запись
return $this->create([ return $this->create([
'user_id' => $userId, 'user_id' => $userId,
'product_id' => $productId, 'product_id' => $productId,
@@ -90,9 +72,6 @@ class Cart extends Model
]) !== null; ]) !== null;
} }
/**
* Обновить количество товара в корзине
*/
public function updateQuantity(int $userId, int $productId, int $quantity): bool public function updateQuantity(int $userId, int $productId, int $quantity): bool
{ {
$sql = "UPDATE {$this->table} $sql = "UPDATE {$this->table}
@@ -101,27 +80,18 @@ class Cart extends Model
return $this->execute($sql, [$quantity, $userId, $productId]); return $this->execute($sql, [$quantity, $userId, $productId]);
} }
/**
* Удалить товар из корзины
*/
public function removeItem(int $userId, int $productId): bool public function removeItem(int $userId, int $productId): bool
{ {
$sql = "DELETE FROM {$this->table} WHERE user_id = ? AND product_id = ?"; $sql = "DELETE FROM {$this->table} WHERE user_id = ? AND product_id = ?";
return $this->execute($sql, [$userId, $productId]); return $this->execute($sql, [$userId, $productId]);
} }
/**
* Очистить корзину пользователя
*/
public function clearCart(int $userId): bool public function clearCart(int $userId): bool
{ {
$sql = "DELETE FROM {$this->table} WHERE user_id = ?"; $sql = "DELETE FROM {$this->table} WHERE user_id = ?";
return $this->execute($sql, [$userId]); return $this->execute($sql, [$userId]);
} }
/**
* Проверить, есть ли товар в корзине
*/
public function hasItem(int $userId, int $productId): bool public function hasItem(int $userId, int $productId): bool
{ {
$item = $this->findWhere([ $item = $this->findWhere([
@@ -131,9 +101,6 @@ class Cart extends Model
return $item !== null; return $item !== null;
} }
/**
* Получить товар из корзины
*/
public function getItem(int $userId, int $productId): ?array public function getItem(int $userId, int $productId): ?array
{ {
return $this->findWhere([ return $this->findWhere([
@@ -142,4 +109,3 @@ class Cart extends Model
]); ]);
} }
} }

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model; use App\Core\Model;
/**
* Category - модель категории товаров
*/
class Category extends Model class Category extends Model
{ {
protected string $table = 'categories'; protected string $table = 'categories';
protected string $primaryKey = 'category_id'; protected string $primaryKey = 'category_id';
/**
* Получить активные категории
*/
public function getActive(): array public function getActive(): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -23,9 +17,6 @@ class Category extends Model
return $this->query($sql); return $this->query($sql);
} }
/**
* Получить родительские категории (без parent_id)
*/
public function getParent(): array public function getParent(): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -34,9 +25,6 @@ class Category extends Model
return $this->query($sql); return $this->query($sql);
} }
/**
* Получить подкатегории
*/
public function getChildren(int $parentId): array public function getChildren(int $parentId): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -45,17 +33,11 @@ class Category extends Model
return $this->query($sql, [$parentId]); return $this->query($sql, [$parentId]);
} }
/**
* Получить категорию по slug
*/
public function findBySlug(string $slug): ?array public function findBySlug(string $slug): ?array
{ {
return $this->findWhere(['slug' => $slug]); return $this->findWhere(['slug' => $slug]);
} }
/**
* Получить все категории с количеством товаров
*/
public function getAllWithProductCount(): array public function getAllWithProductCount(): array
{ {
$sql = "SELECT c1.*, c2.name as parent_name, $sql = "SELECT c1.*, c2.name as parent_name,
@@ -66,9 +48,6 @@ class Category extends Model
return $this->query($sql); return $this->query($sql);
} }
/**
* Создать категорию
*/
public function createCategory(array $data): ?int public function createCategory(array $data): ?int
{ {
$slug = $this->generateSlug($data['name']); $slug = $this->generateSlug($data['name']);
@@ -83,9 +62,6 @@ class Category extends Model
]); ]);
} }
/**
* Обновить категорию
*/
public function updateCategory(int $id, array $data): bool public function updateCategory(int $id, array $data): bool
{ {
$updateData = [ $updateData = [
@@ -101,23 +77,17 @@ class Category extends Model
return $this->update($id, $updateData); return $this->update($id, $updateData);
} }
/**
* Безопасное удаление категории
*/
public function safeDelete(int $id): array public function safeDelete(int $id): array
{ {
// Проверяем наличие товаров
$sql = "SELECT COUNT(*) as cnt FROM products WHERE category_id = ?"; $sql = "SELECT COUNT(*) as cnt FROM products WHERE category_id = ?";
$result = $this->queryOne($sql, [$id]); $result = $this->queryOne($sql, [$id]);
$productCount = (int) $result['cnt']; $productCount = (int) $result['cnt'];
// Проверяем наличие дочерних категорий
$sql = "SELECT COUNT(*) as cnt FROM {$this->table} WHERE parent_id = ?"; $sql = "SELECT COUNT(*) as cnt FROM {$this->table} WHERE parent_id = ?";
$result = $this->queryOne($sql, [$id]); $result = $this->queryOne($sql, [$id]);
$childCount = (int) $result['cnt']; $childCount = (int) $result['cnt'];
if ($productCount > 0 || $childCount > 0) { if ($productCount > 0 || $childCount > 0) {
// Скрываем вместо удаления
$this->update($id, ['is_active' => false]); $this->update($id, ['is_active' => false]);
return [ return [
'deleted' => false, 'deleted' => false,
@@ -126,19 +96,14 @@ class Category extends Model
]; ];
} }
// Удаляем полностью
$this->delete($id); $this->delete($id);
return ['deleted' => true, 'hidden' => false]; return ['deleted' => true, 'hidden' => false];
} }
/**
* Генерация slug из названия
*/
private function generateSlug(string $name): string private function generateSlug(string $name): string
{ {
$slug = mb_strtolower($name); $slug = mb_strtolower($name);
// Транслитерация
$transliteration = [ $transliteration = [
'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd',
'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i',
@@ -156,4 +121,3 @@ class Category extends Model
return $slug; return $slug;
} }
} }

View File

@@ -5,17 +5,11 @@ namespace App\Models;
use App\Core\Model; use App\Core\Model;
use App\Core\Database; use App\Core\Database;
/**
* Order - модель заказа
*/
class Order extends Model class Order extends Model
{ {
protected string $table = 'orders'; protected string $table = 'orders';
protected string $primaryKey = 'order_id'; protected string $primaryKey = 'order_id';
/**
* Создать заказ из корзины
*/
public function createFromCart(int $userId, array $cartItems, array $orderData): ?array public function createFromCart(int $userId, array $cartItems, array $orderData): ?array
{ {
$db = Database::getInstance(); $db = Database::getInstance();
@@ -23,7 +17,6 @@ class Order extends Model
try { try {
$db->beginTransaction(); $db->beginTransaction();
// Считаем итоговую сумму
$subtotal = 0; $subtotal = 0;
foreach ($cartItems as $item) { foreach ($cartItems as $item) {
$subtotal += $item['price'] * $item['quantity']; $subtotal += $item['price'] * $item['quantity'];
@@ -33,10 +26,8 @@ class Order extends Model
$deliveryPrice = (float) ($orderData['delivery_price'] ?? 2000); $deliveryPrice = (float) ($orderData['delivery_price'] ?? 2000);
$finalAmount = $subtotal - $discountAmount + $deliveryPrice; $finalAmount = $subtotal - $discountAmount + $deliveryPrice;
// Генерируем номер заказа
$orderNumber = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999); $orderNumber = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999);
// Создаем заказ
$orderId = $this->create([ $orderId = $this->create([
'user_id' => $userId, 'user_id' => $userId,
'order_number' => $orderNumber, 'order_number' => $orderNumber,
@@ -61,18 +52,15 @@ class Order extends Model
throw new \Exception('Не удалось создать заказ'); throw new \Exception('Не удалось создать заказ');
} }
// Добавляем товары в заказ
foreach ($cartItems as $item) { foreach ($cartItems as $item) {
$this->addOrderItem($orderId, $item); $this->addOrderItem($orderId, $item);
} }
// Уменьшаем количество товаров на складе
$productModel = new Product(); $productModel = new Product();
foreach ($cartItems as $item) { foreach ($cartItems as $item) {
$productModel->decreaseStock($item['product_id'], $item['quantity']); $productModel->decreaseStock($item['product_id'], $item['quantity']);
} }
// Очищаем корзину
$cartModel = new Cart(); $cartModel = new Cart();
$cartModel->clearCart($userId); $cartModel->clearCart($userId);
@@ -90,9 +78,6 @@ class Order extends Model
} }
} }
/**
* Добавить товар в заказ
*/
private function addOrderItem(int $orderId, array $item): void private function addOrderItem(int $orderId, array $item): void
{ {
$sql = "INSERT INTO order_items $sql = "INSERT INTO order_items
@@ -111,9 +96,6 @@ class Order extends Model
]); ]);
} }
/**
* Получить заказ с подробностями
*/
public function getWithDetails(int $orderId): ?array public function getWithDetails(int $orderId): ?array
{ {
$sql = "SELECT o.*, u.email as user_email, u.full_name as user_full_name $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; return $order;
} }
/**
* Получить товары заказа
*/
public function getOrderItems(int $orderId): array public function getOrderItems(int $orderId): array
{ {
$sql = "SELECT oi.*, p.image_url $sql = "SELECT oi.*, p.image_url
@@ -142,9 +121,6 @@ class Order extends Model
return $this->query($sql, [$orderId]); return $this->query($sql, [$orderId]);
} }
/**
* Получить заказы пользователя
*/
public function getUserOrders(int $userId, int $limit = 50): array public function getUserOrders(int $userId, int $limit = 50): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -154,9 +130,6 @@ class Order extends Model
return $this->query($sql, [$userId, $limit]); return $this->query($sql, [$userId, $limit]);
} }
/**
* Получить все заказы для админки
*/
public function getAllForAdmin(int $limit = 50): array public function getAllForAdmin(int $limit = 50): array
{ {
$sql = "SELECT o.*, u.email as user_email $sql = "SELECT o.*, u.email as user_email
@@ -167,9 +140,6 @@ class Order extends Model
return $this->query($sql, [$limit]); return $this->query($sql, [$limit]);
} }
/**
* Обновить статус заказа
*/
public function updateStatus(int $orderId, string $status): bool public function updateStatus(int $orderId, string $status): bool
{ {
$updateData = [ $updateData = [
@@ -184,25 +154,19 @@ class Order extends Model
return $this->update($orderId, $updateData); return $this->update($orderId, $updateData);
} }
/**
* Получить статистику заказов
*/
public function getStats(): array public function getStats(): array
{ {
$stats = []; $stats = [];
// Общее количество заказов
$result = $this->queryOne("SELECT COUNT(*) as cnt FROM {$this->table}"); $result = $this->queryOne("SELECT COUNT(*) as cnt FROM {$this->table}");
$stats['total'] = (int) $result['cnt']; $stats['total'] = (int) $result['cnt'];
// Выручка
$result = $this->queryOne( $result = $this->queryOne(
"SELECT COALESCE(SUM(final_amount), 0) as revenue "SELECT COALESCE(SUM(final_amount), 0) as revenue
FROM {$this->table} WHERE status = 'completed'" FROM {$this->table} WHERE status = 'completed'"
); );
$stats['revenue'] = (float) $result['revenue']; $stats['revenue'] = (float) $result['revenue'];
// По статусам
$statuses = $this->query( $statuses = $this->query(
"SELECT status, COUNT(*) as cnt FROM {$this->table} GROUP BY status" "SELECT status, COUNT(*) as cnt FROM {$this->table} GROUP BY status"
); );
@@ -214,4 +178,3 @@ class Order extends Model
return $stats; return $stats;
} }
} }

View File

@@ -4,17 +4,11 @@ namespace App\Models;
use App\Core\Model; use App\Core\Model;
/**
* Product - модель товара
*/
class Product extends Model class Product extends Model
{ {
protected string $table = 'products'; protected string $table = 'products';
protected string $primaryKey = 'product_id'; protected string $primaryKey = 'product_id';
/**
* Получить товар с категорией
*/
public function findWithCategory(int $id): ?array public function findWithCategory(int $id): ?array
{ {
$sql = "SELECT p.*, c.name as category_name, c.slug as category_slug $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]); return $this->queryOne($sql, [$id]);
} }
/**
* Получить доступные товары
*/
public function getAvailable(array $filters = [], int $limit = 50): array public function getAvailable(array $filters = [], int $limit = 50): array
{ {
$sql = "SELECT p.*, c.name as category_name $sql = "SELECT p.*, c.name as category_name
@@ -35,13 +26,11 @@ class Product extends Model
WHERE p.is_available = TRUE"; WHERE p.is_available = TRUE";
$params = []; $params = [];
// Фильтр по категории
if (!empty($filters['category_id'])) { if (!empty($filters['category_id'])) {
$sql .= " AND p.category_id = ?"; $sql .= " AND p.category_id = ?";
$params[] = $filters['category_id']; $params[] = $filters['category_id'];
} }
// Фильтр по цене
if (!empty($filters['min_price'])) { if (!empty($filters['min_price'])) {
$sql .= " AND p.price >= ?"; $sql .= " AND p.price >= ?";
$params[] = $filters['min_price']; $params[] = $filters['min_price'];
@@ -51,21 +40,18 @@ class Product extends Model
$params[] = $filters['max_price']; $params[] = $filters['max_price'];
} }
// Фильтр по цветам
if (!empty($filters['colors']) && is_array($filters['colors'])) { if (!empty($filters['colors']) && is_array($filters['colors'])) {
$placeholders = implode(',', array_fill(0, count($filters['colors']), '?')); $placeholders = implode(',', array_fill(0, count($filters['colors']), '?'));
$sql .= " AND p.color IN ({$placeholders})"; $sql .= " AND p.color IN ({$placeholders})";
$params = array_merge($params, $filters['colors']); $params = array_merge($params, $filters['colors']);
} }
// Фильтр по материалам
if (!empty($filters['materials']) && is_array($filters['materials'])) { if (!empty($filters['materials']) && is_array($filters['materials'])) {
$placeholders = implode(',', array_fill(0, count($filters['materials']), '?')); $placeholders = implode(',', array_fill(0, count($filters['materials']), '?'));
$sql .= " AND p.material IN ({$placeholders})"; $sql .= " AND p.material IN ({$placeholders})";
$params = array_merge($params, $filters['materials']); $params = array_merge($params, $filters['materials']);
} }
// Поиск по названию/описанию
if (!empty($filters['search'])) { if (!empty($filters['search'])) {
$sql .= " AND (p.name ILIKE ? OR p.description ILIKE ?)"; $sql .= " AND (p.name ILIKE ? OR p.description ILIKE ?)";
$search = '%' . $filters['search'] . '%'; $search = '%' . $filters['search'] . '%';
@@ -79,9 +65,6 @@ class Product extends Model
return $this->query($sql, $params); return $this->query($sql, $params);
} }
/**
* Получить все товары (включая недоступные) для админки
*/
public function getAllForAdmin(bool $showAll = true): array public function getAllForAdmin(bool $showAll = true): array
{ {
$sql = "SELECT p.*, c.name as category_name $sql = "SELECT p.*, c.name as category_name
@@ -97,9 +80,6 @@ class Product extends Model
return $this->query($sql); return $this->query($sql);
} }
/**
* Получить похожие товары
*/
public function getSimilar(int $productId, int $categoryId, int $limit = 3): array public function getSimilar(int $productId, int $categoryId, int $limit = 3): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -111,9 +91,6 @@ class Product extends Model
return $this->query($sql, [$categoryId, $productId, $limit]); return $this->query($sql, [$categoryId, $productId, $limit]);
} }
/**
* Получить доступные цвета
*/
public function getAvailableColors(): array public function getAvailableColors(): array
{ {
$sql = "SELECT DISTINCT color FROM {$this->table} $sql = "SELECT DISTINCT color FROM {$this->table}
@@ -123,9 +100,6 @@ class Product extends Model
return array_column($result, 'color'); return array_column($result, 'color');
} }
/**
* Получить доступные материалы
*/
public function getAvailableMaterials(): array public function getAvailableMaterials(): array
{ {
$sql = "SELECT DISTINCT material FROM {$this->table} $sql = "SELECT DISTINCT material FROM {$this->table}
@@ -135,9 +109,6 @@ class Product extends Model
return array_column($result, 'material'); return array_column($result, 'material');
} }
/**
* Создать товар
*/
public function createProduct(array $data): ?int public function createProduct(array $data): ?int
{ {
$slug = $this->generateSlug($data['name']); $slug = $this->generateSlug($data['name']);
@@ -161,9 +132,6 @@ class Product extends Model
]); ]);
} }
/**
* Обновить товар
*/
public function updateProduct(int $id, array $data): bool public function updateProduct(int $id, array $data): bool
{ {
$updateData = [ $updateData = [
@@ -183,9 +151,6 @@ class Product extends Model
return $this->update($id, $updateData); return $this->update($id, $updateData);
} }
/**
* Уменьшить количество товара на складе
*/
public function decreaseStock(int $productId, int $quantity): bool public function decreaseStock(int $productId, int $quantity): bool
{ {
$sql = "UPDATE {$this->table} $sql = "UPDATE {$this->table}
@@ -195,18 +160,12 @@ class Product extends Model
return $this->execute($sql, [$quantity, $productId]); return $this->execute($sql, [$quantity, $productId]);
} }
/**
* Проверить наличие товара
*/
public function checkStock(int $productId, int $quantity): bool public function checkStock(int $productId, int $quantity): bool
{ {
$product = $this->find($productId); $product = $this->find($productId);
return $product && $product['is_available'] && $product['stock_quantity'] >= $quantity; return $product && $product['is_available'] && $product['stock_quantity'] >= $quantity;
} }
/**
* Генерация slug
*/
private function generateSlug(string $name): string private function generateSlug(string $name): string
{ {
$slug = mb_strtolower($name); $slug = mb_strtolower($name);
@@ -227,13 +186,9 @@ class Product extends Model
return trim($slug, '-'); return trim($slug, '-');
} }
/**
* Генерация артикула
*/
private function generateSku(string $name): string private function generateSku(string $name): string
{ {
$prefix = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $name), 0, 6)); $prefix = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $name), 0, 6));
return 'PROD-' . $prefix . '-' . rand(100, 999); return 'PROD-' . $prefix . '-' . rand(100, 999);
} }
} }

View File

@@ -4,46 +4,30 @@ namespace App\Models;
use App\Core\Model; use App\Core\Model;
/**
* User - модель пользователя
*/
class User extends Model class User extends Model
{ {
protected string $table = 'users'; protected string $table = 'users';
protected string $primaryKey = 'user_id'; protected string $primaryKey = 'user_id';
/**
* Найти пользователя по email
*/
public function findByEmail(string $email): ?array public function findByEmail(string $email): ?array
{ {
return $this->findWhere(['email' => $email]); return $this->findWhere(['email' => $email]);
} }
/**
* Проверить пароль пользователя
*/
public function verifyPassword(string $password, string $hash): bool public function verifyPassword(string $password, string $hash): bool
{ {
return password_verify($password, $hash); return password_verify($password, $hash);
} }
/**
* Хешировать пароль
*/
public function hashPassword(string $password): string public function hashPassword(string $password): string
{ {
return password_hash($password, PASSWORD_DEFAULT); return password_hash($password, PASSWORD_DEFAULT);
} }
/**
* Создать нового пользователя
*/
public function register(array $data): ?int public function register(array $data): ?int
{ {
$config = require dirname(__DIR__, 2) . '/config/app.php'; $config = require dirname(__DIR__, 2) . '/config/app.php';
// Проверяем, является ли email администраторским
$isAdmin = in_array(strtolower($data['email']), $config['admin_emails'] ?? []); $isAdmin = in_array(strtolower($data['email']), $config['admin_emails'] ?? []);
return $this->create([ return $this->create([
@@ -52,14 +36,11 @@ class User extends Model
'full_name' => $data['full_name'], 'full_name' => $data['full_name'],
'phone' => $data['phone'] ?? null, 'phone' => $data['phone'] ?? null,
'city' => $data['city'] ?? null, 'city' => $data['city'] ?? null,
'is_admin' => $isAdmin, 'is_admin' => $isAdmin ? 'true' : 'false',
'is_active' => true 'is_active' => 'true'
]); ]);
} }
/**
* Авторизация пользователя
*/
public function authenticate(string $email, string $password): ?array public function authenticate(string $email, string $password): ?array
{ {
$user = $this->findByEmail($email); $user = $this->findByEmail($email);
@@ -76,7 +57,6 @@ class User extends Model
return null; return null;
} }
// Обновляем время последнего входа
$this->update($user['user_id'], [ $this->update($user['user_id'], [
'last_login' => date('Y-m-d H:i:s') 'last_login' => date('Y-m-d H:i:s')
]); ]);
@@ -84,9 +64,6 @@ class User extends Model
return $user; return $user;
} }
/**
* Получить активных пользователей
*/
public function getActive(int $limit = 50): array public function getActive(int $limit = 50): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -96,9 +73,6 @@ class User extends Model
return $this->query($sql, [$limit]); return $this->query($sql, [$limit]);
} }
/**
* Получить всех пользователей с пагинацией
*/
public function getAllPaginated(int $limit = 50, int $offset = 0): array public function getAllPaginated(int $limit = 50, int $offset = 0): array
{ {
$sql = "SELECT * FROM {$this->table} $sql = "SELECT * FROM {$this->table}
@@ -107,18 +81,12 @@ class User extends Model
return $this->query($sql, [$limit, $offset]); return $this->query($sql, [$limit, $offset]);
} }
/**
* Проверить существование email
*/
public function emailExists(string $email): bool public function emailExists(string $email): bool
{ {
$user = $this->findByEmail($email); $user = $this->findByEmail($email);
return $user !== null; return $user !== null;
} }
/**
* Обновить профиль пользователя
*/
public function updateProfile(int $userId, array $data): bool public function updateProfile(int $userId, array $data): bool
{ {
$allowedFields = ['full_name', 'phone', 'city']; $allowedFields = ['full_name', 'phone', 'city'];
@@ -128,9 +96,6 @@ class User extends Model
return $this->update($userId, $updateData); return $this->update($userId, $updateData);
} }
/**
* Изменить пароль
*/
public function changePassword(int $userId, string $newPassword): bool public function changePassword(int $userId, string $newPassword): bool
{ {
return $this->update($userId, [ return $this->update($userId, [
@@ -139,9 +104,6 @@ class User extends Model
]); ]);
} }
/**
* Заблокировать/разблокировать пользователя
*/
public function setActive(int $userId, bool $active): bool public function setActive(int $userId, bool $active): bool
{ {
return $this->update($userId, [ return $this->update($userId, [
@@ -150,4 +112,3 @@ class User extends Model
]); ]);
} }
} }

View File

@@ -2,12 +2,12 @@
<h2><?= $isEdit ? 'Редактирование категории' : 'Добавление категории' ?></h2> <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> Назад к списку <i class="fas fa-arrow-left"></i> Назад к списку
</a> </a>
<div class="form-container"> <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"> <div class="form-group">
<label>Название категории *</label> <label>Название категории *</label>
<input type="text" name="name" class="form-control" <input type="text" name="name" class="form-control"

View File

@@ -1,6 +1,6 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление категориями</h2> <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> Добавить категорию <i class="fas fa-plus"></i> Добавить категорию
</a> </a>
</div> </div>
@@ -42,10 +42,10 @@
</td> </td>
<td> <td>
<div class="action-buttons"> <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> <i class="fas fa-edit"></i>
</a> </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('Удалить категорию?');"> onsubmit="return confirm('Удалить категорию?');">
<button type="submit" class="btn btn-sm btn-danger" title="Удалить"> <button type="submit" class="btn btn-sm btn-danger" title="Удалить">
<i class="fas fa-trash"></i> <i class="fas fa-trash"></i>

View File

@@ -28,16 +28,16 @@
<div style="margin-top: 30px;"> <div style="margin-top: 30px;">
<h3>Быстрые действия</h3> <h3>Быстрые действия</h3>
<div style="display: flex; gap: 15px; margin-top: 15px; flex-wrap: wrap;"> <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> Добавить товар <i class="fas fa-plus"></i> Добавить товар
</a> </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> Добавить категорию <i class="fas fa-plus"></i> Добавить категорию
</a> </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> Просмотреть заказы <i class="fas fa-shopping-cart"></i> Просмотреть заказы
</a> </a>
<a href="/cite_practica/catalog" class="btn btn-primary"> <a href="/catalog" class="btn btn-primary">
<i class="fas fa-store"></i> Перейти в каталог <i class="fas fa-store"></i> Перейти в каталог
</a> </a>
</div> </div>

View File

@@ -1,6 +1,6 @@
<?php use App\Core\View; ?> <?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> Назад к заказам <i class="fas fa-arrow-left"></i> Назад к заказам
</a> </a>
@@ -22,7 +22,7 @@
<div class="form-container"> <div class="form-container">
<h3>Статус заказа</h3> <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"> <div class="form-group">
<select name="status" class="form-control"> <select name="status" class="form-control">
<option value="pending" <?= $order['status'] === 'pending' ? 'selected' : '' ?>>Ожидает</option> <option value="pending" <?= $order['status'] === 'pending' ? 'selected' : '' ?>>Ожидает</option>
@@ -60,7 +60,7 @@
<?php foreach ($order['items'] as $item): ?> <?php foreach ($order['items'] as $item): ?>
<tr> <tr>
<td> <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;"> style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td> </td>
<td><?= htmlspecialchars($item['product_name']) ?></td> <td><?= htmlspecialchars($item['product_name']) ?></td>

View File

@@ -50,7 +50,7 @@
</span> </span>
</td> </td>
<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> Подробнее <i class="fas fa-eye"></i> Подробнее
</a> </a>
</td> </td>

View File

@@ -2,12 +2,12 @@
<h2><?= $isEdit ? 'Редактирование товара' : 'Добавление товара' ?></h2> <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> Назад к списку <i class="fas fa-arrow-left"></i> Назад к списку
</a> </a>
<div class="form-container"> <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"> <div class="form-group">
<label>Название товара *</label> <label>Название товара *</label>
<input type="text" name="name" class="form-control" <input type="text" name="name" class="form-control"

View File

@@ -2,7 +2,7 @@
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;"> <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление товарами</h2> <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> Добавить товар <i class="fas fa-plus"></i> Добавить товар
</a> </a>
</div> </div>
@@ -16,8 +16,8 @@
<?php endif; ?> <?php endif; ?>
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<a href="/cite_practica/admin/products" class="btn btn-sm <?= !$showAll ? 'btn-primary' : '' ?>">Активные</a> <a href="/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?show_all=1" class="btn btn-sm <?= $showAll ? 'btn-primary' : '' ?>">Все товары</a>
</div> </div>
<table> <table>
@@ -38,7 +38,7 @@
<tr style="<?= !$product['is_available'] ? 'opacity: 0.5;' : '' ?>"> <tr style="<?= !$product['is_available'] ? 'opacity: 0.5;' : '' ?>">
<td><?= $product['product_id'] ?></td> <td><?= $product['product_id'] ?></td>
<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;"> style="width: 50px; height: 50px; object-fit: cover; border-radius: 4px;">
</td> </td>
<td><?= htmlspecialchars($product['name']) ?></td> <td><?= htmlspecialchars($product['name']) ?></td>
@@ -54,13 +54,13 @@
</td> </td>
<td> <td>
<div class="action-buttons"> <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> <i class="fas fa-eye"></i>
</a> </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> <i class="fas fa-edit"></i>
</a> </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('Скрыть товар?');"> onsubmit="return confirm('Скрыть товар?');">
<button type="submit" class="btn btn-sm btn-danger" title="Скрыть"> <button type="submit" class="btn btn-sm btn-danger" title="Скрыть">
<i class="fas fa-eye-slash"></i> <i class="fas fa-eye-slash"></i>

View File

@@ -47,7 +47,7 @@
<div class="auth-actions"> <div class="auth-actions">
<span class="auth-text">Нет аккаунта?</span> <span class="auth-text">Нет аккаунта?</span>
<a href="/cite_practica/register" class="login-btn">Зарегистрироваться</a> <a href="/register" class="login-btn">Зарегистрироваться</a>
</div> </div>
</form> </form>
</div> </div>
@@ -70,13 +70,13 @@ $(document).ready(function() {
} }
$.ajax({ $.ajax({
url: '/cite_practica/login', url: '/login',
method: 'POST', method: 'POST',
data: { email: email, password: password, redirect: redirect }, data: { email: email, password: password, redirect: redirect },
dataType: 'json', dataType: 'json',
success: function(result) { success: function(result) {
if (result.success) { if (result.success) {
window.location.href = result.redirect || '/cite_practica/catalog'; window.location.href = result.redirect || '/catalog';
} else { } else {
alert(result.message || 'Ошибка авторизации'); alert(result.message || 'Ошибка авторизации');
} }
@@ -88,4 +88,3 @@ $(document).ready(function() {
}); });
}); });
</script> </script>

View File

@@ -18,10 +18,6 @@
</div> </div>
<?php endif; ?> <?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-container">
<div class="profile-left-col"> <div class="profile-left-col">
<div class="logo" style="color: white;">AETERNA</div> <div class="logo" style="color: white;">AETERNA</div>
@@ -39,8 +35,11 @@
<div class="profile-right-col"> <div class="profile-right-col">
<div class="profile-form-block"> <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> <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"> <div class="input-group">
<label for="fio">ФИО *</label> <label for="fio">ФИО *</label>
<input type="text" id="fio" name="fio" placeholder="Введите ваше ФИО" <input type="text" id="fio" name="fio" placeholder="Введите ваше ФИО"
@@ -82,7 +81,7 @@
</label> </label>
</div> </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> </a>
@@ -92,4 +91,3 @@
</div> </div>
</div> </div>
</main> </main>

View File

@@ -40,7 +40,7 @@ use App\Core\View;
<main class="container"> <main class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Корзина</span> <a href="/">Главная</a> • <span class="current-page">Корзина</span>
</div> </div>
<h2 style="color: #453227; margin: 20px 0;">Товары в корзине</h2> <h2 style="color: #453227; margin: 20px 0;">Товары в корзине</h2>
@@ -49,7 +49,7 @@ use App\Core\View;
<div class="empty-cart"> <div class="empty-cart">
<i class="fas fa-shopping-cart" style="font-size: 48px; color: #ccc; margin-bottom: 20px;"></i> <i class="fas fa-shopping-cart" style="font-size: 48px; color: #ccc; margin-bottom: 20px;"></i>
<p>Ваша корзина пуста</p> <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> </a>
</div> </div>
@@ -60,7 +60,17 @@ use App\Core\View;
<?php foreach ($cartItems as $item): ?> <?php foreach ($cartItems as $item): ?>
<div class="products__item" data-product-id="<?= $item['product_id'] ?>" data-price="<?= $item['price'] ?>"> <div class="products__item" data-product-id="<?= $item['product_id'] ?>" data-price="<?= $item['price'] ?>">
<div class="products__image"> <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']) ?>"> alt="<?= htmlspecialchars($item['name']) ?>">
</div> </div>
<div class="products__details"> <div class="products__details">
@@ -170,13 +180,12 @@ use App\Core\View;
<script> <script>
$(document).ready(function() { $(document).ready(function() {
// Обновление количества
$('.products__qty-btn').on('click', function(e) { $('.products__qty-btn').on('click', function(e) {
e.preventDefault(); e.preventDefault();
const productId = $(this).data('id'); var productId = $(this).data('id');
const isPlus = $(this).hasClass('plus'); var isPlus = $(this).hasClass('plus');
const $qtyValue = $(this).siblings('.products__qty-value'); var $qtyValue = $(this).siblings('.products__qty-value');
let quantity = parseInt($qtyValue.text()); var quantity = parseInt($qtyValue.text());
if (isPlus) { quantity++; } if (isPlus) { quantity++; }
else if (quantity > 1) { quantity--; } else if (quantity > 1) { quantity--; }
@@ -185,7 +194,7 @@ $(document).ready(function() {
$qtyValue.text(quantity); $qtyValue.text(quantity);
$.ajax({ $.ajax({
url: '/cite_practica/cart/update', url: '/cart/update',
method: 'POST', method: 'POST',
data: { product_id: productId, quantity: quantity }, data: { product_id: productId, quantity: quantity },
dataType: 'json', dataType: 'json',
@@ -198,16 +207,15 @@ $(document).ready(function() {
}); });
}); });
// Удаление товара
$('.remove-from-cart').on('click', function(e) { $('.remove-from-cart').on('click', function(e) {
e.preventDefault(); e.preventDefault();
const productId = $(this).data('id'); var productId = $(this).data('id');
const $item = $(this).closest('.products__item'); var $item = $(this).closest('.products__item');
if (!confirm('Удалить товар из корзины?')) return; if (!confirm('Удалить товар из корзины?')) return;
$.ajax({ $.ajax({
url: '/cite_practica/cart/remove', url: '/cart/remove',
method: 'POST', method: 'POST',
data: { product_id: productId }, data: { product_id: productId },
dataType: 'json', dataType: 'json',
@@ -227,31 +235,30 @@ $(document).ready(function() {
}); });
function updateTotals() { function updateTotals() {
let productsTotal = 0; var productsTotal = 0;
let totalCount = 0; var totalCount = 0;
$('.products__item').each(function() { $('.products__item').each(function() {
const price = parseInt($(this).data('price')); var price = parseInt($(this).data('price'));
const quantity = parseInt($(this).find('.products__qty-value').text()); var quantity = parseInt($(this).find('.products__qty-value').text());
productsTotal += price * quantity; productsTotal += price * quantity;
totalCount += quantity; totalCount += quantity;
}); });
const delivery = parseFloat($('input[name="delivery_price"]').val()); var delivery = parseFloat($('input[name="delivery_price"]').val());
const discount = parseFloat($('input[name="discount"]').val()); var discount = parseFloat($('input[name="discount"]').val());
const finalTotal = productsTotal + delivery - discount; var finalTotal = productsTotal + delivery - discount;
$('.products-total').text(productsTotal.toLocaleString('ru-RU') + ' ₽'); $('.products-total').text(productsTotal.toLocaleString('ru-RU') + ' ₽');
$('.summary-count').text(totalCount); $('.summary-count').text(totalCount);
$('.final-total').text(finalTotal.toLocaleString('ru-RU') + ' ₽'); $('.final-total').text(finalTotal.toLocaleString('ru-RU') + ' ₽');
} }
// Промокод
$('#applyPromo').click(function() { $('#applyPromo').click(function() {
const promoCode = $('#promo_code').val().toUpperCase(); var promoCode = $('#promo_code').val().toUpperCase();
if (promoCode === 'SALE10') { if (promoCode === 'SALE10') {
const productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, '')); var productsTotal = parseFloat($('.products-total').text().replace(/[^0-9]/g, ''));
const discount = Math.round(productsTotal * 0.1); var discount = Math.round(productsTotal * 0.1);
$('input[name="discount"]').val(discount); $('input[name="discount"]').val(discount);
$('.discount-total').text(discount.toLocaleString('ru-RU') + ' ₽'); $('.discount-total').text(discount.toLocaleString('ru-RU') + ' ₽');
showNotification('Промокод применен! Скидка 10%'); showNotification('Промокод применен! Скидка 10%');
@@ -266,7 +273,6 @@ $(document).ready(function() {
} }
}); });
// Оформление заказа
$('#orderForm').submit(function(e) { $('#orderForm').submit(function(e) {
e.preventDefault(); e.preventDefault();
@@ -278,7 +284,7 @@ $(document).ready(function() {
$('#submit-order').prop('disabled', true).text('ОБРАБОТКА...'); $('#submit-order').prop('disabled', true).text('ОБРАБОТКА...');
$.ajax({ $.ajax({
url: '/cite_practica/order', url: '/order',
method: 'POST', method: 'POST',
data: $(this).serialize(), data: $(this).serialize(),
dataType: 'json', dataType: 'json',
@@ -286,7 +292,7 @@ $(document).ready(function() {
if (result.success) { if (result.success) {
showNotification('Заказ успешно оформлен!'); showNotification('Заказ успешно оформлен!');
setTimeout(function() { setTimeout(function() {
window.location.href = '/cite_practica/'; window.location.href = '/';
}, 1500); }, 1500);
} else { } else {
showNotification('Ошибка: ' + result.message, 'error'); showNotification('Ошибка: ' + result.message, 'error');
@@ -301,4 +307,3 @@ $(document).ready(function() {
}); });
}); });
</script> </script>

View File

@@ -7,10 +7,10 @@
К сожалению, запрошенная страница не существует или была перемещена. К сожалению, запрошенная страница не существует или была перемещена.
</p> </p>
<div style="margin-top: 30px;"> <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> На главную <i class="fas fa-home"></i> На главную
</a> </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> В каталог <i class="fas fa-store"></i> В каталог
</a> </a>
</div> </div>

View File

@@ -7,7 +7,7 @@
Произошла ошибка при обработке вашего запроса. Мы уже работаем над её устранением. Произошла ошибка при обработке вашего запроса. Мы уже работаем над её устранением.
</p> </p>
<div style="margin-top: 30px;"> <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> На главную <i class="fas fa-home"></i> На главную
</a> </a>
</div> </div>

View File

@@ -4,16 +4,16 @@
<div class="container hero__content"> <div class="container hero__content">
<div class="hero__image-block"> <div class="hero__image-block">
<div class="hero__circle"></div> <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>
<div class="hero__text-block"> <div class="hero__text-block">
<h1>ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР</h1> <h1>ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР</h1>
<p class="hero__usp-text">Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.</p> <p class="hero__usp-text">Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.</p>
<?php if ($isLoggedIn): ?> <?php if ($isLoggedIn): ?>
<a href="/cite_practica/catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a> <a href="/catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php else: ?> <?php else: ?>
<a href="/cite_practica/login" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a> <a href="/login" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -44,17 +44,17 @@
<div class="promo-images"> <div class="promo-images">
<div class="promo-image-col"> <div class="promo-image-col">
<img src="/img/спальня.jpg" alt="Кровать и тумба"> <img src="/assets/images/спальня.jpg" alt="Кровать и тумба">
<div class="image-overlay-text"> <div class="image-overlay-text">
<h4>НОВИНКИ В КАТЕГОРИЯХ <br>МЯГКАЯ МЕБЕЛЬ</h4> <h4>НОВИНКИ В КАТЕГОРИЯХ <br>МЯГКАЯ МЕБЕЛЬ</h4>
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a> <a href="/catalog" class="overlay-link">ПЕРЕЙТИ</a>
</div> </div>
</div> </div>
<div class="promo-image-col"> <div class="promo-image-col">
<img src="/img/диван.jpg" alt="Диван в гостиной"> <img src="/assets/images/диван.jpg" alt="Диван в гостиной">
<div class="image-overlay-text"> <div class="image-overlay-text">
<h4>РАСПРОДАЖА <br>ПРЕДМЕТЫ ДЕКОРА</h4> <h4>РАСПРОДАЖА <br>ПРЕДМЕТЫ ДЕКОРА</h4>
<a href="/cite_practica/catalog" class="overlay-link">ПЕРЕЙТИ</a> <a href="/catalog" class="overlay-link">ПЕРЕЙТИ</a>
</div> </div>
</div> </div>
</div> </div>
@@ -68,11 +68,11 @@
<h2>О НАС</h2> <h2>О НАС</h2>
<p class="text-justified">Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.</p> <p class="text-justified">Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.</p>
</div> </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>
<div class="about__column about__column--right"> <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> <p class="about__caption">Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.</p>
</div> </div>
</div> </div>
@@ -83,12 +83,20 @@
<div class="solutions-slider"> <div class="solutions-slider">
<div class="solutions-slider__slides"> <div class="solutions-slider__slides">
<div class="solutions-slider__slide"> <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"> <div class="solution-text-overlay">
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ ГОСТИНОЙ</h2><br> <h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ ГОСТИНОЙ</h2><br>
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p> <p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
</div> </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> </div>
</div> </div>
@@ -150,4 +158,3 @@
<button class="btn primary-btn">Задать вопрос</button> <button class="btn primary-btn">Задать вопрос</button>
</div> </div>
</section> </section>

View File

@@ -44,25 +44,25 @@
<h1><i class="fas fa-user-shield"></i> Админ-панель AETERNA</h1> <h1><i class="fas fa-user-shield"></i> Админ-панель AETERNA</h1>
<div> <div>
<span><?= htmlspecialchars($user['email'] ?? 'Администратор') ?></span> <span><?= htmlspecialchars($user['email'] ?? 'Администратор') ?></span>
<a href="/cite_practica/catalog" class="btn btn-primary" style="margin-left: 10px;">В каталог</a> <a href="/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="/logout" class="btn btn-danger" style="margin-left: 10px;">Выйти</a>
</div> </div>
</div> </div>
<div class="admin-tabs"> <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> Дашборд <i class="fas fa-tachometer-alt"></i> Дашборд
</a> </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> Товары <i class="fas fa-box"></i> Товары
</a> </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> Категории <i class="fas fa-tags"></i> Категории
</a> </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> Заказы <i class="fas fa-shopping-cart"></i> Заказы
</a> </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> Пользователи <i class="fas fa-users"></i> Пользователи
</a> </a>
</div> </div>
@@ -72,4 +72,3 @@
</div> </div>
</body> </body>
</html> </html>

View File

@@ -47,16 +47,16 @@
<?= \App\Core\View::partial('footer') ?> <?= \App\Core\View::partial('footer') ?>
<script> <script>
function showNotification(message, type = 'success') { function showNotification(message, type) {
const notification = $('#notification'); type = type || 'success';
var notification = $('#notification');
notification.text(message); notification.text(message);
notification.removeClass('success error').addClass(type + ' show'); notification.removeClass('success error').addClass(type + ' show');
setTimeout(function() { notification.removeClass('show'); }, 3000); setTimeout(function() { notification.removeClass('show'); }, 3000);
} }
$(document).ready(function() { $(document).ready(function() {
// Обновляем счетчик корзины $.get('/cart/count', function(response) {
$.get('/cite_practica/cart/count', function(response) {
if (response.success) { if (response.success) {
$('.cart-count').text(response.cart_count); $('.cart-count').text(response.cart_count);
} }
@@ -65,4 +65,3 @@
</script> </script>
</body> </body>
</html> </html>

View File

@@ -3,7 +3,7 @@
<main class="delivery-page"> <main class="delivery-page">
<div class="container"> <div class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Доставка и оплата</span> <a href="/">Главная</a> • <span class="current-page">Доставка и оплата</span>
</div> </div>
<h1 style="color: #453227; margin: 30px 0;">Доставка и оплата</h1> <h1 style="color: #453227; margin: 30px 0;">Доставка и оплата</h1>

View File

@@ -3,7 +3,7 @@
<main class="services-page"> <main class="services-page">
<div class="container"> <div class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Услуги</span> <a href="/">Главная</a> • <span class="current-page">Услуги</span>
</div> </div>
<h1 style="color: #453227; margin: 30px 0;">Наши услуги</h1> <h1 style="color: #453227; margin: 30px 0;">Наши услуги</h1>

View File

@@ -3,7 +3,7 @@
<main class="warranty-page"> <main class="warranty-page">
<div class="container"> <div class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Гарантия и возврат</span> <a href="/">Главная</a> • <span class="current-page">Гарантия и возврат</span>
</div> </div>
<h1 style="color: #453227; margin: 30px 0;">Гарантия и возврат</h1> <h1 style="color: #453227; margin: 30px 0;">Гарантия и возврат</h1>

View File

@@ -7,17 +7,17 @@
<div class="footer__col"> <div class="footer__col">
<h5>ПОКУПАТЕЛЮ</h5> <h5>ПОКУПАТЕЛЮ</h5>
<ul> <ul>
<li><a href="/cite_practica/catalog">Каталог</a></li> <li><a href="/catalog">Каталог</a></li>
<li><a href="/cite_practica/services">Услуги</a></li> <li><a href="/services">Услуги</a></li>
</ul> </ul>
</div> </div>
<div class="footer__col"> <div class="footer__col">
<h5>ПОМОЩЬ</h5> <h5>ПОМОЩЬ</h5>
<ul> <ul>
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li> <li><a href="/delivery">Доставка и оплата</a></li>
<li><a href="/cite_practica/warranty">Гарантия и возврат</a></li> <li><a href="/warranty">Гарантия и возврат</a></li>
<li><a href="/cite_practica/#faq">Ответы на вопросы</a></li> <li><a href="/#faq">Ответы на вопросы</a></li>
<li><a href="#footer">Контакты</a></li> <li><a href="#footer">Контакты</a></li>
</ul> </ul>
</div> </div>
@@ -42,7 +42,6 @@
</div> </div>
</div> </div>
<div class="copyright"> <div class="copyright">
<p>© 2025 AETERNA. Все права защищены.</p> <p>© <?= date('Y') ?> AETERNA. Все права защищены.</p>
</div> </div>
</footer> </footer>

View File

@@ -6,24 +6,24 @@ $user = $user ?? \App\Core\View::currentUser();
<header class="header"> <header class="header">
<div class="header__top"> <div class="header__top">
<div class="container header__top-content"> <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="search-catalog">
<div class="catalog-dropdown"> <div class="catalog-dropdown">
Все категории <span>&#9660;</span> Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu"> <div class="catalog-dropdown__menu">
<ul> <ul>
<li><a href="/cite_practica/catalog">Все товары</a></li> <li><a href="/catalog">Все товары</a></li>
<li><a href="/cite_practica/catalog?category=1">Диваны</a></li> <li><a href="/catalog?category=1">Диваны</a></li>
<li><a href="/cite_practica/catalog?category=2">Кровати</a></li> <li><a href="/catalog?category=2">Кровати</a></li>
<li><a href="/cite_practica/catalog?category=3">Шкафы</a></li> <li><a href="/catalog?category=3">Шкафы</a></li>
<li><a href="/cite_practica/catalog?category=4">Стулья</a></li> <li><a href="/catalog?category=4">Стулья</a></li>
<li><a href="/cite_practica/catalog?category=5">Столы</a></li> <li><a href="/catalog?category=5">Столы</a></li>
</ul> </ul>
</div> </div>
</div> </div>
<div class="search-box"> <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;"> <input type="text" name="search" placeholder="Поиск товаров" style="border: none; width: 100%; padding: 10px;">
<button type="submit" style="background: none; border: none; cursor: pointer;"> <button type="submit" style="background: none; border: none; cursor: pointer;">
<span class="search-icon"><i class="fas fa-search"></i></span> <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"> <div class="header__icons--top">
<?php if ($isLoggedIn): ?> <?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> <i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span> <span class="cart-count">0</span>
</a> </a>
@@ -68,17 +68,17 @@ $user = $user ?? \App\Core\View::currentUser();
</div> </div>
<ul class="user-profile-links"> <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): ?> <?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; ?> <?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> </ul>
</div> </div>
</div> </div>
<?php else: ?> <?php else: ?>
<a href="/cite_practica/login" class="icon"><i class="far fa-user"></i></a> <a href="/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" style="font-size: 12px; color: #666; margin-left: 5px;">Войти</a>
<?php endif; ?> <?php endif; ?>
</div> </div>
</div> </div>
@@ -87,7 +87,7 @@ $user = $user ?? \App\Core\View::currentUser();
<div class="header__bottom"> <div class="header__bottom">
<div class="container header__bottom-content"> <div class="container header__bottom-content">
<div class="catalog-menu"> <div class="catalog-menu">
<a href="/cite_practica/catalog" class="catalog-link"> <a href="/catalog" class="catalog-link">
<span class="catalog-lines">☰</span> <span class="catalog-lines">☰</span>
Каталог Каталог
</a> </a>
@@ -95,10 +95,10 @@ $user = $user ?? \App\Core\View::currentUser();
<nav class="nav"> <nav class="nav">
<ul class="nav-list"> <ul class="nav-list">
<li><a href="/cite_practica/">Главная</a></li> <li><a href="/">Главная</a></li>
<li><a href="/cite_practica/services">Услуги</a></li> <li><a href="/services">Услуги</a></li>
<li><a href="/cite_practica/delivery">Доставка и оплата</a></li> <li><a href="/delivery">Доставка и оплата</a></li>
<li><a href="/cite_practica/warranty">Гарантия</a></li> <li><a href="/warranty">Гарантия</a></li>
<li><a href="#footer">Контакты</a></li> <li><a href="#footer">Контакты</a></li>
</ul> </ul>
</nav> </nav>
@@ -106,4 +106,3 @@ $user = $user ?? \App\Core\View::currentUser();
</div> </div>
</div> </div>
</header> </header>

View File

@@ -33,7 +33,7 @@ use App\Core\View;
<main class="catalog-main"> <main class="catalog-main">
<div class="container"> <div class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <span class="current-page">Каталог</span> <a href="/">Главная</a> • <span class="current-page">Каталог</span>
</div> </div>
<?php if (!empty($success)): ?> <?php if (!empty($success)): ?>
@@ -48,10 +48,10 @@ use App\Core\View;
<i class="fas fa-user-shield"></i> Панель управления каталогом <i class="fas fa-user-shield"></i> Панель управления каталогом
</h3> </h3>
<div> <div>
<a href="/cite_practica/admin/products" class="admin-btn"><i class="fas fa-boxes"></i> Управление каталогом</a> <a href="/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="/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="/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/orders" class="admin-btn"><i class="fas fa-shopping-cart"></i> Заказы</a>
</div> </div>
</div> </div>
<?php endif; ?> <?php endif; ?>
@@ -67,14 +67,14 @@ use App\Core\View;
<div class="catalog-wrapper"> <div class="catalog-wrapper">
<aside class="catalog-sidebar"> <aside class="catalog-sidebar">
<form method="GET" action="/cite_practica/catalog" id="filterForm"> <form method="GET" action="/catalog" id="filterForm">
<div class="filter-group"> <div class="filter-group">
<h4 class="filter-title">КАТЕГОРИИ</h4> <h4 class="filter-title">КАТЕГОРИИ</h4>
<ul class="filter-list"> <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): ?> <?php foreach ($categories as $category): ?>
<li> <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' : '' ?>"> class="<?= $filters['category_id'] == $category['category_id'] ? 'active-category' : '' ?>">
<?= htmlspecialchars($category['name']) ?> <?= htmlspecialchars($category['name']) ?>
</a> </a>
@@ -129,7 +129,7 @@ use App\Core\View;
<?php if (!empty($filters['search'])): ?> <?php if (!empty($filters['search'])): ?>
<p style="color: #666;"> <p style="color: #666;">
Результаты поиска: "<strong><?= htmlspecialchars($filters['search']) ?></strong>" Результаты поиска: "<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> Очистить <i class="fas fa-times"></i> Очистить
</a> </a>
</p> </p>
@@ -144,9 +144,19 @@ use App\Core\View;
<?php else: ?> <?php else: ?>
<?php foreach ($products as $product): ?> <?php foreach ($products as $product): ?>
<div class="product-card <?= !$product['is_available'] ? 'unavailable' : '' ?>" <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'] ?>"> 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']) ?>"> alt="<?= htmlspecialchars($product['name']) ?>">
<div class="product-info"> <div class="product-info">
<div class="name"><?= htmlspecialchars($product['name']) ?></div> <div class="name"><?= htmlspecialchars($product['name']) ?></div>
@@ -174,7 +184,7 @@ $('#priceSlider').on('input', function() {
function addToCart(productId, productName) { function addToCart(productId, productName) {
$.ajax({ $.ajax({
url: '/cite_practica/cart/add', url: '/cart/add',
method: 'POST', method: 'POST',
data: { product_id: productId, quantity: 1 }, data: { product_id: productId, quantity: 1 },
dataType: 'json', dataType: 'json',
@@ -192,4 +202,3 @@ function addToCart(productId, productName) {
}); });
} }
</script> </script>

View File

@@ -35,10 +35,10 @@ use App\Core\View;
<main class="container"> <main class="container">
<div class="breadcrumbs"> <div class="breadcrumbs">
<a href="/cite_practica/">Главная</a> • <a href="/">Главная</a> •
<a href="/cite_practica/catalog">Каталог</a> • <a href="/catalog">Каталог</a> •
<?php if ($product['category_name']): ?> <?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']) ?> <?= htmlspecialchars($product['category_name']) ?>
</a> • </a> •
<?php endif; ?> <?php endif; ?>
@@ -48,7 +48,17 @@ use App\Core\View;
<div class="product__section"> <div class="product__section">
<div class="product__gallery"> <div class="product__gallery">
<div class="product__main-image"> <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']) ?>"> alt="<?= htmlspecialchars($product['name']) ?>">
</div> </div>
</div> </div>
@@ -134,7 +144,7 @@ use App\Core\View;
<?php if ($isAdmin): ?> <?php if ($isAdmin): ?>
<div style="margin-top: 20px;"> <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> Редактировать <i class="fas fa-edit"></i> Редактировать
</a> </a>
</div> </div>
@@ -147,8 +157,18 @@ use App\Core\View;
<h2>Похожие товары</h2> <h2>Похожие товары</h2>
<div class="products-grid"> <div class="products-grid">
<?php foreach ($similarProducts as $similar): ?> <?php foreach ($similarProducts as $similar): ?>
<div class="product-card" onclick="window.location.href='/cite_practica/product/<?= $similar['product_id'] ?>'" style="cursor: pointer;"> <?php
<img src="/cite_practica/<?= htmlspecialchars($similar['image_url'] ?? 'img/1.jpg') ?>" $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']) ?>"> alt="<?= htmlspecialchars($similar['name']) ?>">
<div class="product-info"> <div class="product-info">
<h3 style="font-size: 16px; color: #453227;"><?= htmlspecialchars($similar['name']) ?></h3> <h3 style="font-size: 16px; color: #453227;"><?= htmlspecialchars($similar['name']) ?></h3>
@@ -180,7 +200,7 @@ $(document).ready(function() {
function addToCart(productId) { function addToCart(productId) {
const quantity = $('.product__qty-value').val(); const quantity = $('.product__qty-value').val();
$.ajax({ $.ajax({
url: '/cite_practica/cart/add', url: '/cart/add',
method: 'POST', method: 'POST',
data: { product_id: productId, quantity: quantity }, data: { product_id: productId, quantity: quantity },
dataType: 'json', dataType: 'json',
@@ -198,13 +218,12 @@ function addToCart(productId) {
function buyNow(productId) { function buyNow(productId) {
const quantity = $('.product__qty-value').val(); const quantity = $('.product__qty-value').val();
$.ajax({ $.ajax({
url: '/cite_practica/cart/add', url: '/cart/add',
method: 'POST', method: 'POST',
data: { product_id: productId, quantity: quantity }, data: { product_id: productId, quantity: quantity },
success: function() { success: function() {
window.location.href = '/cite_practica/cart'; window.location.href = '/cart';
} }
}); });
} }
</script> </script>

File diff suppressed because it is too large Load Diff

View File

@@ -1,924 +0,0 @@
<?php
// catalog_admin.php - единый файл каталога с админ-панелью
session_start();
// Подключение к базе данных
require_once 'config/database.php';
// Проверка прав администратора
$is_admin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
$action = $_GET['action'] ?? '';
$product_id = $_GET['id'] ?? 0;
$category_id = $_GET['category'] ?? '';
// Если не админ, перенаправляем
if (!$is_admin) {
header('Location: вход.php');
exit();
}
try {
$db = Database::getInstance()->getConnection();
// ОБРАБОТКА POST ЗАПРОСОВ ДЛЯ КАТЕГОРИЙ
if ($_SERVER['REQUEST_METHOD'] === 'POST' && $is_admin) {
$form_action = $_POST['action'] ?? '';
if ($form_action === 'add_category' || $form_action === 'edit_category') {
$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;
$category_id_post = (int)($_POST['category_id'] ?? 0);
// Валидация
if (empty($name)) {
$_SESSION['error'] = 'Название категории обязательно';
header('Location: catalog_admin.php?action=' . $form_action . ($category_id_post ? '&id=' . $category_id_post : ''));
exit();
}
// Создаем slug
$slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
$slug = preg_replace('/^-+|-+$/', '', $slug); // Убираем дефисы по краям
if ($form_action === 'add_category') {
// Проверяем существование slug
$check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ?");
$check->execute([$slug]);
if ($check->fetchColumn() > 0) {
$slug = $slug . '-' . time(); // Добавляем timestamp для уникальности
}
$sql = "INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
VALUES (?, ?, ?, ?, ?, ?)";
$stmt = $db->prepare($sql);
$stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
$_SESSION['message'] = 'Категория успешно добавлена';
header('Location: catalog_admin.php?action=categories');
exit();
} elseif ($form_action === 'edit_category' && $category_id_post > 0) {
// Проверяем существование slug для других категорий
$check = $db->prepare("SELECT COUNT(*) FROM categories WHERE slug = ? AND category_id != ?");
$check->execute([$slug, $category_id_post]);
if ($check->fetchColumn() > 0) {
$slug = $slug . '-' . $category_id_post;
}
$sql = "UPDATE categories SET
name = ?, slug = ?, parent_id = ?, description = ?,
sort_order = ?, is_active = ?, updated_at = CURRENT_TIMESTAMP
WHERE category_id = ?";
$stmt = $db->prepare($sql);
$stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id_post]);
$_SESSION['message'] = 'Категория успешно обновлена';
header('Location: catalog_admin.php?action=categories');
exit();
}
}
// Удаление категории
if ($form_action === 'delete_category') {
$category_id_del = (int)($_POST['category_id'] ?? 0);
if ($category_id_del > 0) {
// Проверяем наличие активных товаров
$check_products = $db->prepare("
SELECT COUNT(*) as product_count
FROM products
WHERE category_id = ? AND is_available = TRUE
");
$check_products->execute([$category_id_del]);
$active_products = $check_products->fetchColumn();
if ($active_products > 0) {
$_SESSION['error'] = 'Невозможно удалить категорию с активными товарами';
header('Location: catalog_admin.php?action=categories');
exit();
}
// Проверяем дочерние категории
$check_children = $db->prepare("
SELECT COUNT(*) as child_count
FROM categories
WHERE parent_id = ? AND is_active = TRUE
");
$check_children->execute([$category_id_del]);
$active_children = $check_children->fetchColumn();
if ($active_children > 0) {
$_SESSION['error'] = 'Невозможно удалить категорию с активными дочерними категориями';
header('Location: catalog_admin.php?action=categories');
exit();
}
// Удаляем категорию
$stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
$stmt->execute([$category_id_del]);
$_SESSION['message'] = 'Категория успешно удалена';
header('Location: catalog_admin.php?action=categories');
exit();
}
}
}
// Получаем категории для отображения
$categories_stmt = $db->query("
SELECT c1.*, c2.name as parent_name,
(SELECT COUNT(*) FROM products WHERE 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
");
$categories = $categories_stmt->fetchAll();
// Для редактирования категории
if ($action === 'edit_category' && $product_id > 0) {
$cat_stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?");
$cat_stmt->execute([$product_id]);
$current_category = $cat_stmt->fetch();
if (!$current_category) {
$_SESSION['error'] = 'Категория не найдена';
header('Location: catalog_admin.php?action=categories');
exit();
}
}
// Получаем товары
$sql = "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";
$params = [];
if ($category_id && is_numeric($category_id)) {
$sql .= " AND p.category_id = ?";
$params[] = $category_id;
}
$sql .= " ORDER BY p.product_id DESC";
if ($params) {
$stmt = $db->prepare($sql);
$stmt->execute($params);
} else {
$stmt = $db->query($sql);
}
$products = $stmt->fetchAll();
// Сообщения из сессии
$message = $_SESSION['message'] ?? '';
$error = $_SESSION['error'] ?? '';
// Очищаем сообщения после использования
unset($_SESSION['message']);
unset($_SESSION['error']);
} catch (PDOException $e) {
$error = "Ошибка подключения к базе данных: " . $e->getMessage();
}
?>
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AETERNA - Каталог</title>
<link rel="stylesheet/less" type="text/css" href="style_for_cite.less">
<script src="https://cdn.jsdelivr.net/npm/less"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
.admin-panel {
background: #f8f9fa;
padding: 20px;
margin: 20px 0;
border-radius: 8px;
border-left: 4px solid #617365;
}
.admin-btn {
background: #617365;
color: white;
padding: 8px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
margin: 5px;
text-decoration: none;
display: inline-block;
}
.admin-btn:hover {
background: #453227;
}
.admin-form {
background: white;
padding: 30px;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
margin: 20px 0;
}
.form-group {
margin-bottom: 15px;
}
.form-group label {
display: block;
margin-bottom: 5px;
font-weight: bold;
}
.form-group input, .form-group textarea, .form-group select {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-family: "Anonymous Pro", monospace;
font-size: 14px;
}
.form-group textarea {
min-height: 100px;
resize: vertical;
}
.form-group input[type="checkbox"] {
width: auto;
display: inline-block;
margin-right: 8px;
}
.alert {
padding: 15px;
margin-bottom: 20px;
border-radius: 4px;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-danger {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.badge {
padding: 3px 8px;
border-radius: 4px;
font-size: 11px;
font-weight: bold;
margin-left: 5px;
}
.badge-warning {
background: #ffc107;
color: #212529;
}
.badge-info {
background: #17a2b8;
color: white;
}
.badge-success {
background: #28a745;
color: white;
}
.badge-danger {
background: #dc3545;
color: white;
}
.btn {
padding: 8px 15px;
border-radius: 4px;
border: none;
cursor: pointer;
font-size: 14px;
text-decoration: none;
display: inline-block;
}
.btn-sm {
padding: 5px 10px;
font-size: 12px;
}
.btn-warning {
background: #ffc107;
color: #212529;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-secondary {
background: #6c757d;
color: white;
opacity: 0.6;
cursor: not-allowed;
}
.btn-success {
background: #28a745;
color: white;
}
.admin-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}
.admin-table th,
.admin-table td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
.admin-table th {
background: #f8f9fa;
font-weight: bold;
color: #453227;
}
.admin-table tr:hover {
background: #f8f9fa;
}
.catalog-dropdown {
position: relative;
}
.catalog-dropdown__menu {
display: none;
position: absolute;
top: 100%;
left: 0;
width: 250px;
background: white;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
padding: 15px;
z-index: 1000;
margin-top: 5px;
max-height: 400px;
overflow-y: auto;
}
.catalog-dropdown:hover .catalog-dropdown__menu {
display: block;
}
.catalog-dropdown__menu ul {
list-style: none;
padding: 0;
margin: 0;
}
.catalog-dropdown__menu li {
padding: 8px 0;
border-bottom: 1px solid #f0f0f0;
}
.catalog-dropdown__menu li:last-child {
border-bottom: none;
}
.catalog-dropdown__menu a {
color: #333;
text-decoration: none;
display: block;
}
.catalog-dropdown__menu a:hover {
color: #453227;
padding-left: 5px;
}
</style>
</head>
<body>
<header class="header">
<div class="header__top">
<div class="container header__top-content">
<div class="logo">AETERNA</div>
<div class="search-catalog">
<div class="catalog-dropdown">
Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu">
<ul>
<li><a href="catalog_admin.php">Все товары</a></li>
<?php foreach ($categories as $cat): ?>
<li><a href="catalog_admin.php?category=<?= $cat['category_id'] ?>">
<?= htmlspecialchars($cat['name']) ?>
</a></li>
<?php endforeach; ?>
</ul>
</div>
</div>
<div class="search-box">
<input type="text" placeholder="Поиск товаров">
<span class="search-icon"><i class="fas fa-search"></i></span>
</div>
</div>
<div class="header__icons--top">
<a href="оформлениеаказа.php" class="icon cart-icon">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
</a>
<?php if ($is_admin): ?>
<div class="user-profile-dropdown">
<div class="user-profile-toggle">
<div class="user-avatar">
<?= strtoupper(substr($_SESSION['user_email'] ?? 'A', 0, 1)) ?>
</div>
<div class="user-info">
<div class="user-email"><?= htmlspecialchars($_SESSION['user_email'] ?? '') ?></div>
<div class="user-status admin">Админ</div>
</div>
<i class="fas fa-chevron-down dropdown-arrow"></i>
</div>
<div class="user-profile-menu">
<div class="user-profile-header">
<div class="user-profile-name">
<i class="fas fa-user-shield"></i>
<?= htmlspecialchars($_SESSION['full_name'] ?? $_SESSION['user_email']) ?>
</div>
</div>
<ul class="user-profile-links">
<li><a href="logout.php" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>Выйти</span>
</a></li>
</ul>
</div>
</div>
<?php endif; ?>
</div>
</div>
</div>
<div class="header__bottom">
<div class="container header__bottom-content">
<div class="catalog-menu">
<a href="catalog_admin.php" class="catalog-link active-catalog">
<div class="catalog-icon">
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</div>
<span class="catalog-lines">☰</span>
Каталог
</a>
</div>
<nav class="nav">
<ul class="nav-list">
<li><a href="cite_mebel.php">Главная</a></li>
<li><a href="услуги.php">Услуги</a></li>
<li><a href="Доставка.php">Доставка и оплата</a></li>
<li><a href="Гарантия.php">Гарантия</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</nav>
<div class="header-phone">+7(912)999-12-23</div>
</div>
</div>
</header>
<main class="catalog-main">
<div class="container">
<div class="breadcrumbs">
<a href="cite_mebel.php">Главная</a> • <span class="current-page">Каталог</span>
</div>
<?php if ($is_admin): ?>
<!-- Панель администратора -->
<div class="admin-panel">
<h3>Панель управления каталогом</h3>
<a href="?action=add_product" class="admin-btn">
<i class="fas fa-plus"></i> Добавить товар
</a>
<a href="?action=add_category" class="admin-btn">
<i class="fas fa-folder-plus"></i> Добавить категорию
</a>
<a href="?action=categories" class="admin-btn" style="background: #ffc107; color: #212529;">
<i class="fas fa-tags"></i> Управление категориями
</a>
</div>
<?php if ($message): ?>
<div class="alert alert-success">
<i class="fas fa-check-circle"></i> <?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<?php if ($error): ?>
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<?php if ($action === 'add_category' || $action === 'edit_category'): ?>
<!-- Форма добавления/редактирования категории -->
<div class="admin-form" id="categoryForm">
<h3><?= ($action === 'add_category') ? 'Добавление категории' : 'Редактирование категории' ?></h3>
<?php if (isset($error) && !empty($error)): ?>
<div class="alert alert-danger">
<i class="fas fa-exclamation-circle"></i> <?= htmlspecialchars($error) ?>
</div>
<?php endif; ?>
<form method="POST" action="catalog_admin.php" id="categoryFormElement">
<input type="hidden" name="action" value="<?= $action ?>">
<?php if ($action === 'edit_category' && isset($current_category)): ?>
<input type="hidden" name="category_id" value="<?= $current_category['category_id'] ?>">
<?php endif; ?>
<div class="form-group">
<label for="category_name">Название категории: *</label>
<input type="text" name="name" id="category_name" required
value="<?= htmlspecialchars($current_category['name'] ?? '') ?>"
placeholder="Введите название категории">
</div>
<div class="form-group">
<label for="parent_category">Родительская категория:</label>
<select name="parent_id" id="parent_category">
<option value="">Без родительской категории</option>
<?php foreach ($categories as $cat): ?>
<?php if (!isset($current_category['category_id']) || $cat['category_id'] != $current_category['category_id']): ?>
<option value="<?= $cat['category_id'] ?>"
<?= (isset($current_category['parent_id']) && $current_category['parent_id'] == $cat['category_id']) ? 'selected' : '' ?>>
<?= htmlspecialchars($cat['name']) ?>
</option>
<?php endif; ?>
<?php endforeach; ?>
</select>
</div>
<div class="form-group">
<label for="category_description">Описание:</label>
<textarea name="description" id="category_description" rows="3"
placeholder="Описание категории (необязательно)"><?= htmlspecialchars($current_category['description'] ?? '') ?></textarea>
</div>
<div class="form-group">
<label for="sort_order">Порядок сортировки:</label>
<input type="number" name="sort_order" id="sort_order"
value="<?= $current_category['sort_order'] ?? 0 ?>"
min="0" max="100">
</div>
<div class="form-group">
<label>
<input type="checkbox" name="is_active" value="1"
<?= (!isset($current_category['is_active']) || $current_category['is_active']) ? 'checked' : '' ?>>
Активна
</label>
</div>
<button type="submit" class="admin-btn">
<i class="fas fa-save"></i> Сохранить
</button>
<a href="catalog_admin.php?action=categories" class="admin-btn" style="background: #6c757d;">
<i class="fas fa-times"></i> Отмена
</a>
</form>
</div>
<?php endif; ?>
<?php endif; ?>
<div class="catalog-wrapper">
<aside class="catalog-sidebar">
<div class="filter-group">
<h4 class="filter-title">КАТЕГОРИИ</h4>
<ul class="filter-list">
<li><a href="catalog_admin.php" class="<?= (!$category_id) ? 'active-category' : '' ?>">Все товары</a></li>
<?php foreach ($categories as $cat): ?>
<li>
<a href="catalog_admin.php?category=<?= $cat['category_id'] ?>"
class="<?= ($category_id == $cat['category_id']) ? 'active-category' : '' ?>">
<?= htmlspecialchars($cat['name']) ?>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<div class="filter-group">
<h4 class="filter-title">ЦЕНА</h4>
<div class="price-range">
<div class="range-slider">
<input type="range" min="1000" max="100000" value="50000" step="1000">
</div>
<div class="price-display">До 50 000 ₽</div>
</div>
</div>
<button class="btn primary-btn filter-apply-btn">ПРИМЕНИТЬ</button>
</aside>
<section class="catalog-products">
<?php if ($action === 'categories'): ?>
<!-- Раздел управления категориями -->
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
<h2>Управление категориями</h2>
<a href="?action=add_category" class="btn btn-success">
<i class="fas fa-plus"></i> Добавить категорию
</a>
</div>
<table class="admin-table">
<thead>
<tr>
<th>ID</th>
<th>Название</th>
<th>Родительская</th>
<th>Товаров</th>
<th>Порядок</th>
<th>Статус</th>
<th>Действия</th>
</tr>
</thead>
<tbody>
<?php foreach ($categories as $cat): ?>
<tr>
<td><?= $cat['category_id'] ?></td>
<td>
<strong><?= htmlspecialchars($cat['name']) ?></strong>
<br><small style="color: #666;"><?= htmlspecialchars($cat['slug']) ?></small>
</td>
<td><?= htmlspecialchars($cat['parent_name'] ?? '—') ?></td>
<td>
<span class="badge badge-info"><?= $cat['product_count'] ?> товаров</span>
</td>
<td><?= $cat['sort_order'] ?></td>
<td>
<?php if ($cat['is_active']): ?>
<span class="badge badge-success">Активна</span>
<?php else: ?>
<span class="badge badge-warning">Неактивна</span>
<?php endif; ?>
</td>
<td>
<a href="?action=edit_category&id=<?= $cat['category_id'] ?>"
class="btn btn-sm btn-warning" title="Редактировать">
<i class="fas fa-edit"></i>
</a>
<form method="POST" action="catalog_admin.php" style="display: inline-block;"
onsubmit="return confirm('Вы уверены, что хотите удалить категорию?');">
<input type="hidden" name="action" value="delete_category">
<input type="hidden" name="category_id" value="<?= $cat['category_id'] ?>">
<?php if ($cat['product_count'] == 0): ?>
<button type="submit" class="btn btn-sm btn-danger" title="Удалить">
<i class="fas fa-trash"></i>
</button>
<?php else: ?>
<button type="button" class="btn btn-sm btn-secondary"
title="Нельзя удалить категорию с товарами" disabled>
<i class="fas fa-trash"></i>
</button>
<?php endif; ?>
</form>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php else: ?>
<!-- Основной каталог товаров -->
<div class="products-container">
<?php foreach ($products as $product): ?>
<div class="product-card <?= getCardSizeClass($product) ?>">
<?php if ($is_admin): ?>
<div class="admin-actions">
<a href="?action=edit&id=<?= $product['product_id'] ?>"
class="admin-btn" style="background: #28a745;">
<i class="fas fa-edit"></i>
</a>
<a href="?action=delete&id=<?= $product['product_id'] ?>"
class="admin-btn" style="background: #dc3545;"
onclick="return confirm('Сделать товар недоступным?')">
<i class="fas fa-trash"></i>
</a>
</div>
<?php endif; ?>
<div class="product-image-container">
<img src="<?= htmlspecialchars($product['image_url'] ?: 'img1/default.jpg') ?>"
alt="<?= htmlspecialchars($product['name']) ?>"
class="product-img">
<?php if ($product['old_price'] && $product['old_price'] > $product['price']): ?>
<span class="product-discount">
-<?= round(($product['old_price'] - $product['price']) / $product['old_price'] * 100) ?>%
</span>
<?php endif; ?>
<i class="fas fa-shopping-cart product-wishlist-icon"
onclick="addToCart(<?= $product['product_id'] ?>)"></i>
</div>
<div class="product-info" style="padding: 15px;">
<div class="product-name"><?= htmlspecialchars($product['name']) ?></div>
<div class="product-details">
<?= htmlspecialchars(mb_substr($product['description'], 0, 100)) ?>...
</div>
<div class="product-price" style="margin-top: 10px;">
<?php if ($product['old_price'] && $product['old_price'] > $product['price']): ?>
<span style="text-decoration: line-through; color: #999; font-size: 14px;">
<?= number_format($product['old_price'], 0, '', ' ') ?> ₽
</span><br>
<?php endif; ?>
<?= number_format($product['price'], 0, '', ' ') ?> ₽
</div>
<div class="product-stock" style="font-size: 12px; color: #28a745; margin-top: 5px;">
В наличии: <?= $product['stock_quantity'] ?> шт.
</div>
<button onclick="addToCart(<?= $product['product_id'] ?>)"
class="btn btn-primary" style="width: 100%; margin-top: 10px;">
<i class="fas fa-shopping-cart"></i> В корзину
</button>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</section>
</div>
</div>
</main>
<footer class="footer" id="footer">
<div class="container footer__content">
<div class="footer__col footer--logo">
<div class="logo">AETERNA</div>
</div>
<div class="footer__col">
<h5>ПОКУПАТЕЛЮ</h5>
<ul>
<li><a href="catalog_admin.php">Каталог</a></li>
<li><a href="услуги.php">Услуги</a></li>
</ul>
</div>
<div class="footer__col">
<h5>ПОМОЩЬ</h5>
<ul>
<li><a href="Доставка.php">Доставка и оплата</a></li>
<li><a href="Гарантия.php">Гарантия и возврат</a></li>
<li><a href="cite_mebel.php#faq">Ответы на вопросы</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</div>
<div class="footer__col">
<h5>КОНТАКТЫ</h5>
<p>aeterna@mail.ru</p>
<p>+7(912)999-12-23</p>
<div class="social-icons">
<span class="icon"><i class="fab fa-telegram"></i></span>
<span class="icon"><i class="fab fa-instagram"></i></span>
<span class="icon"><i class="fab fa-vk"></i></span>
</div>
</div>
<div class="footer__col">
<h5>ПРИНИМАЕМ К ОПЛАТЕ</h5>
<div class="payment-icons">
<span class="pay-icon"><i class="fab fa-cc-visa"></i></span>
<span class="pay-icon"><i class="fab fa-cc-mastercard"></i></span>
</div>
</div>
</div>
<div class="copyright">
<p>© 2025 AETERNA. Все права защищены.</p>
</div>
</footer>
<script>
// Функция для показа формы добавления товара
function showAddForm() {
window.location.href = 'catalog_admin.php?action=add';
}
// Функция для показа формы добавления категории
function showAddCategoryForm() {
window.location.href = 'catalog_admin.php?action=add_category';
}
// Функция для редактирования категории
function editCategory(categoryId) {
window.location.href = 'catalog_admin.php?action=edit_category&id=' + categoryId;
}
// Функция для скрытия формы
function hideForm() {
window.location.href = 'catalog_admin.php?action=categories';
}
// Функция добавления в корзину
function addToCart(productId) {
$.ajax({
url: "cart_handler.php",
method: "POST",
data: { action: "add", product_id: productId, quantity: 1 },
success: function(response) {
try {
var result = JSON.parse(response);
if (result.success) {
alert("Товар добавлен в корзину!");
// Обновляем счетчик корзины
if ($(".cart-count").length) {
var current = parseInt($(".cart-count").text()) || 0;
$(".cart-count").text(current + 1);
}
} else {
alert("Ошибка: " + result.message);
}
} catch(e) {
alert("Товар добавлен в корзину!");
}
},
error: function() {
alert("Товар добавлен в корзину!");
}
});
}
// Обработка формы категории с подтверждением
$(document).ready(function() {
$('#categoryFormElement').on('submit', function(e) {
const action = $(this).find('input[name="action"]').val();
const categoryName = $(this).find('input[name="name"]').val();
if (!categoryName.trim()) {
e.preventDefault();
alert('Пожалуйста, введите название категории');
return false;
}
const message = action === 'add_category'
? 'Добавить новую категорию "' + categoryName + '"?'
: 'Сохранить изменения в категории?';
if (!confirm(message)) {
e.preventDefault();
return false;
}
});
// Подтверждение удаления категории
$('form[action="catalog_admin.php"]').on('submit', function(e) {
const action = $(this).find('input[name="action"]').val();
if (action === 'delete_category') {
const categoryId = $(this).find('input[name="category_id"]').val();
if (!confirm('Вы уверены, что хотите удалить эту категорию?')) {
e.preventDefault();
return false;
}
}
});
<?php if ($is_admin): ?>
$('.product-card').hover(
function() {
$(this).find('.admin-actions').show();
},
function() {
$(this).find('.admin-actions').hide();
}
);
<?php endif; ?>
});
</script>
</body>
</html>
<?php
// Вспомогательная функция для определения размера карточки
function getCardSizeClass($product) {
$sizes = ['small', 'large', 'wide', 'tall', 'small1', 'wide2', 'wide3', 'wide2_1', 'full-width'];
$index = $product['product_id'] % count($sizes);
return $sizes[$index];
}

View File

@@ -1,111 +0,0 @@
<?php
// catalog_admin_action.php
session_start();
require_once 'config/database.php';
if (!isset($_SESSION['is_admin']) || $_SESSION['is_admin'] !== true) {
header('Location: вход.php');
exit();
}
$db = Database::getInstance()->getConnection();
$action = $_POST['action'] ?? '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
try {
switch ($action) {
case 'add_category':
$name = $_POST['name'] ?? '';
$parent_id = $_POST['parent_id'] ?: null;
$description = $_POST['description'] ?? null;
$sort_order = $_POST['sort_order'] ?? 0;
$is_active = isset($_POST['is_active']) ? 1 : 0;
// Создаем slug из названия
$slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
$stmt = $db->prepare("
INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active)
VALUES (?, ?, ?, ?, ?, ?)
");
$stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]);
header('Location: catalog_admin.php?action=categories&message=Категория успешно добавлена');
exit();
case 'edit_category':
$category_id = $_POST['category_id'] ?? 0;
$name = $_POST['name'] ?? '';
$parent_id = $_POST['parent_id'] ?: null;
$description = $_POST['description'] ?? null;
$sort_order = $_POST['sort_order'] ?? 0;
$is_active = isset($_POST['is_active']) ? 1 : 0;
// Создаем slug из названия
$slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name));
$stmt = $db->prepare("
UPDATE categories SET
name = ?,
slug = ?,
parent_id = ?,
description = ?,
sort_order = ?,
is_active = ?
WHERE category_id = ?
");
$stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]);
header('Location: catalog_admin.php?action=categories&message=Категория успешно обновлена');
exit();
case 'delete_category':
$category_id = $_POST['category_id'] ?? 0;
// Проверяем, есть ли активные товары в этой категории
$checkStmt = $db->prepare("
SELECT COUNT(*)
FROM products
WHERE category_id = ? AND is_available = TRUE
");
$checkStmt->execute([$category_id]);
$active_products = $checkStmt->fetchColumn();
if ($active_products > 0) {
header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными товарами');
exit();
}
// Проверяем дочерние категории
$checkChildStmt = $db->prepare("
SELECT COUNT(*)
FROM categories
WHERE parent_id = ? AND is_active = TRUE
");
$checkChildStmt->execute([$category_id]);
$active_children = $checkChildStmt->fetchColumn();
if ($active_children > 0) {
header('Location: catalog_admin.php?action=categories&error=Невозможно удалить категорию с активными дочерними категориями');
exit();
}
// Удаляем категорию
$stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?");
$stmt->execute([$category_id]);
header('Location: catalog_admin.php?action=categories&message=Категория успешно удалена');
exit();
}
} catch (PDOException $e) {
header('Location: catalog_admin.php?action=categories&error=' . urlencode('Ошибка базы данных: ' . $e->getMessage()));
exit();
}
}
header('Location: catalog_admin.php');
exit();
?>

View File

@@ -1,17 +0,0 @@
<?php
// check_admin.php
session_start();
function checkAdmin() {
if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
header('Location: вход.php?error=admin_required');
exit();
}
return true;
}
// Возвращает true если пользователь администратор
function isAdmin() {
return isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
}
?>

View File

@@ -1,21 +0,0 @@
<?php
// check_auth_status.php
session_start();
$response = [
'loggedIn' => isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true
];
if ($response['loggedIn']) {
$response['user'] = [
'user_id' => $_SESSION['user_id'] ?? 0,
'email' => $_SESSION['user_email'] ?? '',
'full_name' => $_SESSION['full_name'] ?? '',
'is_admin' => $_SESSION['isAdmin'] ?? false,
'login_time' => $_SESSION['login_time'] ?? time()
];
}
header('Content-Type: application/json');
echo json_encode($response);
?>

View File

@@ -1,51 +0,0 @@
<?php
require_once 'config/database.php';
$db = Database::getInstance()->getConnection();
echo "<h2>Проверка категорий в базе данных</h2>";
try {
$stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id");
$categories = $stmt->fetchAll();
if (empty($categories)) {
echo "<p style='color: red;'>Категорий нет! Нужно сначала добавить категории.</p>";
// Добавим тестовые категории
$insert_sql = "
INSERT INTO categories (name, slug, parent_id, description) VALUES
('Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'),
('Диваны', 'divany', 1, 'Прямые и угловые диваны'),
('Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'),
('Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'),
('Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати')
RETURNING category_id
";
$db->exec($insert_sql);
echo "<p style='color: green;'>Добавлены тестовые категории</p>";
// Снова проверим
$stmt = $db->query("SELECT category_id, name, slug, parent_id FROM categories ORDER BY category_id");
$categories = $stmt->fetchAll();
}
echo "<table border='1' cellpadding='5'>";
echo "<tr><th>ID</th><th>Название</th><th>Slug</th><th>Родитель</th></tr>";
foreach ($categories as $category) {
echo "<tr>";
echo "<td>" . $category['category_id'] . "</td>";
echo "<td>" . htmlspecialchars($category['name']) . "</td>";
echo "<td>" . htmlspecialchars($category['slug']) . "</td>";
echo "<td>" . ($category['parent_id'] ?: '-') . "</td>";
echo "</tr>";
}
echo "</table>";
} catch (PDOException $e) {
echo "Ошибка: " . $e->getMessage();
}
?>

View File

@@ -1,762 +0,0 @@
<?php
session_start();
// Упрощенная проверка для каталога
if (isset($_GET['action']) && $_GET['action'] == 'go_to_catalog') {
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
// Перенаправляем на вход
header('Location: вход.php?redirect=' . urlencode('catalog.php'));
exit();
} else {
// Если авторизован - пускаем в каталог
header('Location: catalog.php');
exit();
}
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>AETERNA - Мебель и Интерьер</title>
<link rel="stylesheet/less" type="text/css" href="style_for_cite.less">
<script src="https://cdn.jsdelivr.net/npm/less"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css">
<style>
/* Стили для профиля пользователя */
.user-profile-dropdown {
position: relative;
display: inline-block;
}
.user-profile-toggle {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
padding: 8px 12px;
border-radius: 4px;
transition: all 0.3s ease;
}
.user-profile-toggle:hover {
background-color: rgba(0, 0, 0, 0.05);
}
.user-avatar {
width: 36px;
height: 36px;
border-radius: 50%;
background: linear-gradient(135deg, #617365 0%, #453227 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 16px;
}
.user-info {
display: flex;
flex-direction: column;
}
.user-email {
font-size: 12px;
color: #666;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.user-status {
font-size: 10px;
padding: 2px 8px;
border-radius: 12px;
text-transform: uppercase;
font-weight: bold;
text-align: center;
margin-top: 2px;
}
.user-status.admin {
background-color: #617365;
color: white;
}
.user-status.user {
background-color: #28a745;
color: white;
}
.dropdown-arrow {
font-size: 12px;
color: #666;
}
.user-profile-menu {
display: none;
position: absolute;
top: 100%;
right: 0;
background: white;
min-width: 280px;
border-radius: 8px;
box-shadow: 0 5px 20px rgba(0,0,0,0.15);
z-index: 1000;
margin-top: 10px;
border: 1px solid #e0e0e0;
overflow: hidden;
}
.user-profile-header {
padding: 15px;
background: #f8f9fa;
border-bottom: 1px solid #e0e0e0;
}
.user-profile-name {
font-weight: bold;
margin-bottom: 5px;
color: #333;
display: flex;
align-items: center;
gap: 8px;
}
.user-profile-details {
font-size: 12px;
color: #666;
}
.user-profile-details small {
display: block;
margin-bottom: 3px;
}
.user-profile-details i {
width: 16px;
text-align: center;
}
.user-profile-links {
list-style: none;
padding: 10px 0;
margin: 0;
}
.user-profile-links li {
margin: 0;
}
.user-profile-links a {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
color: #333;
text-decoration: none;
transition: all 0.3s ease;
border-left: 3px solid transparent;
}
.user-profile-links a:hover {
background-color: #f5f5f5;
border-left-color: #453227;
color: #453227;
}
.user-profile-links i {
width: 20px;
text-align: center;
}
.logout-item {
border-top: 1px solid #e0e0e0;
margin-top: 5px;
padding-top: 5px;
}
.logout-link {
color: #dc3545 !important;
}
.logout-link:hover {
background-color: #ffe6e6 !important;
border-left-color: #dc3545 !important;
}
.cart-icon {
position: relative;
margin-right: 15px;
}
.cart-count {
position: absolute;
top: -8px;
right: -8px;
background: #dc3545;
color: white;
border-radius: 50%;
width: 18px;
height: 18px;
font-size: 11px;
display: flex;
align-items: center;
justify-content: center;
}
/* Стиль для заблокированных ссылок */
.link-disabled {
cursor: not-allowed !important;
opacity: 0.6 !important;
position: relative;
}
.link-disabled:hover::after {
content: "🔒 Требуется авторизация";
position: absolute;
top: -30px;
left: 50%;
transform: translateX(-50%);
background: #333;
color: white;
padding: 5px 10px;
border-radius: 4px;
font-size: 12px;
white-space: nowrap;
z-index: 1000;
}
/* Кнопка каталога с замком */
.catalog-locked {
position: relative;
}
.catalog-locked::before {
content: "🔒";
position: absolute;
top: -5px;
right: -5px;
font-size: 12px;
}
</style>
</head>
<body>
<header class="header">
<div class="header__top">
<div class="container header__top-content">
<div class="logo">AETERNA</div>
<div class="search-catalog">
<div class="catalog-dropdown">
Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu">
<ul>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php?category=1')">Диваны</a></li>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php?category=2')">Кровати</a></li>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php?category=3')">Шкафы</a></li>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php?category=4')">Стулья</a></li>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php?category=5')">Столы</a></li>
</ul>
</div>
</div>
<div class="search-box">
<input type="text" placeholder="Поиск товаров" id="searchInput">
<span class="search-icon"><i class="fas fa-search"></i></span>
</div>
</div>
<!-- Вставьте этот код в секцию header__icons--top вместо текущего -->
<!-- Вставьте этот код в секцию header__icons--top вместо текущего -->
<div class="header__icons--top">
<?php if (isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true): ?>
<!-- Иконка корзины -->
<a href="оформлениеаказа.php" class="icon cart-icon">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
</a>
<!-- Блок профиля с выпадающим меню -->
<div class="user-profile-dropdown">
<div class="user-profile-toggle">
<div class="user-avatar">
<?php
$email = $_SESSION['user_email'] ?? '';
echo !empty($email) ? strtoupper(substr($email, 0, 1)) : 'U';
?>
</div>
<div class="user-info">
<div class="user-email"><?= htmlspecialchars($email) ?></div>
<div class="user-status <?= isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] ? 'admin' : 'user' ?>">
<?= isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] ? 'Админ' : 'Пользователь' ?>
</div>
</div>
<i class="fas fa-chevron-down dropdown-arrow"></i>
</div>
<div class="user-profile-menu">
<div class="user-profile-header">
<div class="user-profile-name">
<i class="fas fa-user"></i>
<?= htmlspecialchars($_SESSION['full_name'] ?? $email) ?>
</div>
<div class="user-profile-details">
<small><i class="far fa-envelope"></i> <?= htmlspecialchars($email) ?></small>
<?php if (isset($_SESSION['login_time'])): ?>
<small><i class="far fa-clock"></i> Вошел: <?= date('d.m.Y H:i', $_SESSION['login_time']) ?></small>
<?php endif; ?>
</div>
</div>
<ul class="user-profile-links">
<li>
<a href="профиль.php">
<i class="fas fa-user-cog"></i>
<span>Настройки профиля</span>
</a>
</li>
<li>
<a href="оформлениеаказа.php">
<i class="fas fa-shopping-bag"></i>
<span>Мои заказы</span>
</a>
</li>
<?php if (isset($_SESSION['isAdmin']) && $_SESSION['isAdmin']): ?>
<li>
<a href="catalog_admin.php">
<i class="fas fa-user-shield"></i>
<span>Панель администратора</span>
</a>
</li>
<?php endif; ?>
<li class="logout-item">
<a href="logout.php" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>Выйти из аккаунта</span>
</a>
</li>
</ul>
</div>
</div>
<?php else: ?>
<!-- Если не авторизован -->
<a href="вход.php" class="icon">
<i class="far fa-user"></i>
</a>
<a href="вход.php" style="font-size: 12px; color: #666; margin-left: 5px;">
Войти
</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="header__bottom">
<div class="container header__bottom-content">
<div class="catalog-menu">
<a href="catalog.php"
class="catalog-link <?= (isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true) ? '' : 'catalog-locked' ?>">
<div class="catalog-icon">
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</div>
<span class="catalog-lines">☰</span>
Каталог
</a>
</div>
<nav class="nav">
<ul class="nav-list">
<li><a href="cite_mebel.php" class="active">Главная</a></li>
<li><a href="услуги.php">Услуги</a></li>
<li><a href="Доставка.php">Доставка и оплата</a></li>
<li><a href="Гарантия.php">Гарантия</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</nav>
<div class="header-phone">+7(912)999-12-23</div>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="container hero__content">
<div class="hero__image-block">
<div class="hero__circle"></div>
<img src="img/chair.PNG" alt="Кресло и торшер" class="hero__img">
</div>
<div class="hero__text-block">
<h1>ДОБАВЬТЕ ИЗЫСКАННОСТИ В СВОЙ ИНТЕРЬЕР</h1>
<p class="hero__usp-text">Мы создаем мебель, которая сочетает в себе безупречный дизайн, натуральные материалы, продуманный функционал, чтобы ваш день начинался и заканчивался с комфортом.</p>
<?php if (isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true): ?>
<a href="?action=go_to_catalog" class="btn primary-btn">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php else: ?>
<a href="javascript:void(0)" onclick="checkAuth('catalog.php')" class="btn primary-btn link-disabled">ПЕРЕЙТИ В КАТАЛОГ</a>
<?php endif; ?>
</div>
</div>
</section>
<section class="advantages">
<div class="container">
<div class="advantages__header">
<h2>ПОЧЕМУ <br>ВЫБИРАЮТ НАС?</h2>
<div class="advantages__items">
<div class="advantage-item">
<span class="advantage-item__number">1</span>
<h4>ГАРАНТИЯ ВЫСОЧАЙШЕГО КАЧЕСТВА</h4>
<p>Собственное производство и строгий контроль на всех этапах.</p>
</div>
<div class="advantage-item">
<span class="advantage-item__number">2</span>
<h4>ИСПОЛЬЗОВАНИЕ НАДЕЖНЫХ МАТЕРИАЛОВ</h4>
<p>Гарантия безопасности и долговечности.</p>
</div>
<div class="advantage-item">
<span class="advantage-item__number">3</span>
<h4>ИНДИВИДУАЛЬНЫЙ ПОДХОД И ГИБКОСТЬ УСЛОВИЙ</h4>
<p>Реализуем проекты любой сложности по вашим техническим заданиям.</p>
</div>
</div>
</div>
<div class="promo-images">
<div class="promo-image-col">
<img src="img/спальня.jpg" alt="Кровать и тумба">
<div class="image-overlay-text">
<h4>НОВИНКИ В КАТЕГОРИЯХ <br>МЯГКАЯ МЕБЕЛЬ</h4>
<a href="#" class="overlay-link">ПЕРЕЙТИ</a>
</div>
</div>
<div class="promo-image-col">
<img src="img/диван.jpg" alt="Диван в гостиной">
<div class="image-overlay-text">
<h4>РАСПРОДАЖА <br>ПРЕДМЕТЫ ДЕКОРА</h4>
<a href="#" class="overlay-link">ПЕРЕЙТИ</a>
</div>
</div>
</div>
</div>
</section>
<section class="about">
<div class="container about__content">
<div class="about__column about__column--left">
<div class="about__text-block">
<h2>О НАС</h2>
<p class="text-justified">Компания AETERNA - российский производитель качественной корпусной и мягкой мебели для дома и офиса. С 2015 года мы успешно реализуем проекты любой сложности, сочетая современные технологии, проверенные материалы и классическое мастерство.</p>
</div>
<img src="img/кресло_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">
<p class="about__caption">Наша сеть включает 30+ российских фабрик, отобранных по строгим стандартам качества. Мы сотрудничаем исключительно с лидерами рынка, чья продукция доказала свое превосходство временем.</p>
</div>
</div>
</section>
<section class="solutions">
<div class="container">
<div class="solutions-slider">
<div class="solutions-slider__slides">
<div class="solutions-slider__slide">
<img src="img/слайдер_1.jpg" class="solution-img" alt="Готовое решение для гостиной">
<div class="solution-text-overlay">
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ ГОСТИНОЙ</h2> <br>
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
</div>
<a href="#" class="solution-image-link">Подробнее</a>
</div>
<div class="solutions-slider__slide">
<img src="img/слайдер_6.jpg" class="solution-img" alt="Готовое решение для спальни">
<div class="solution-text-overlay">
<h2>ГОТОВОЕ РЕШЕНИЕ<br>ДЛЯ ВАШЕЙ СПАЛЬНИ</h2> <br>
<p>УСПЕЙТЕ ЗАКАЗАТЬ СЕЙЧАС</p>
</div>
<a href="#" class="solution-image-link">Подробнее</a>
</div>
</div>
</div>
</div>
</section>
<section class="stats">
<div class="container">
<div class="stats__items">
<div class="stat-item">
<div class="stat-number">10+</div>
<div class="stat-label">Лет работы</div>
</div>
<div class="stat-item">
<div class="stat-number">30 000+</div>
<div class="stat-label">Довольных покупателей</div>
</div>
<div class="stat-item">
<div class="stat-number">4500+</div>
<div class="stat-label">Реализованных заказов</div>
</div>
</div>
</div>
</section>
<section class="faq" id="faq">
<div class="container">
<h2>ОТВЕТЫ НА ВОПРОСЫ</h2>
<div class="faq__items">
<div class="faq-item">
<span class="number-circle">1</span>
<div class="faq-item__content">
<h4>Сколько времени занимает доставка?</h4>
<p>Доставка готовых позиций занимает 1-3 дня. Мебель на заказ изготавливается от 14 до 45 рабочих дней, в зависимости от сложности. Точные сроки озвучит ваш менеджер при оформлении заказа.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">2</span>
<div class="faq-item__content">
<h4>Нужно ли вносить предоплату?</h4>
<p>Да, для запуска заказа в производство необходима предоплата в размере 50-70% от стоимости, в зависимости от изделия. Оставшаяся сумма оплачивается при доставке и приемке мебели.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">3</span>
<div class="faq-item__content">
<h4>Предоставляется ли рассрочка или кредит?</h4>
<p>Да, мы сотрудничаем с несколькими банками и предлагаем рассрочку на 6 или 12 месяцев без первоначального взноса, а также кредит на более длительный срок. Все условия уточняйте у вашего менеджера.</p>
</div>
</div>
<div class="faq-item">
<span class="number-circle">4</span>
<div class="faq-item__content">
<h4>Что делать, если мебель пришла с дефектом?</h4>
<p>В этом случае необходимо в течение 7 дней со дня доставки сообщить нам о проблеме, прислать фото/видео дефекта. Мы оперативно решим вопрос о бесплатной замене или ремонте изделия.</p>
</div>
</div>
</div>
<button class="btn primary-btn">Задать вопрос</button>
</div>
</section>
</main>
<footer class="footer" id="footer">
<div class="container footer__content">
<div class="footer__col footer--logo">
<div class="logo">AETERNA</div>
</div>
<div class="footer__col">
<h5>ПОКУПАТЕЛЮ</h5>
<ul>
<li><a href="javascript:void(0)" onclick="checkAuth('catalog.php')">Каталог</a></li>
<li><a href="услуги.php">Услуги</a></li>
</ul>
</div>
<div class="footer__col">
<h5>ПОМОЩЬ</h5>
<ul>
<li><a href="Доставка.php">Доставка и оплата</a></li>
<li><a href="Гарантия.php">Гарантия и возврат</a></li>
<li><a href="cite_mebel.html#faq">Ответы на вопросы</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</div>
<div class="footer__col">
<h5>КОНТАКТЫ</h5>
<p>aeterna@mail.ru</p>
<p>+7(912)999-12-23</p>
<div class="social-icons">
<span class="icon"><i class="fab fa-telegram"></i></span>
<span class="icon"><i class="fab fa-instagram"></i></span>
<span class="icon"><i class="fab fa-vk"></i></span>
</div>
</div>
<div class="footer__col">
<h5>ПРИНИМАЕМ К ОПЛАТЕ</h5>
<div class="payment-icons">
<span class="pay-icon"><i class="fab fa-cc-visa"></i></span>
<span class="pay-icon"><i class="fab fa-cc-mastercard"></i></span>
</div>
</div>
</div>
<div class="copyright">
<p>© 2025 AETERNA. Все права защищены.</p>
</div>
</footer>
<!-- Статус пользователя (будет показан после авторизации) -->
<div class="user-status-overlay" id="userStatus" style="display: none; position: fixed; top: 20px; right: 20px; z-index: 1000; background: white; padding: 10px 15px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<div style="display: flex; align-items: center; gap: 10px;">
<i class="fas fa-user" id="statusIcon"></i>
<div>
<div id="statusText" style="font-weight: bold;"></div>
<div id="userEmail" style="font-size: 12px; color: #666;"></div>
</div>
<button onclick="logout()" style="background: none; border: none; color: #666; cursor: pointer; margin-left: 10px;">
<i class="fas fa-sign-out-alt"></i>
</button>
</div>
</div>
<!-- Модальное окно для быстрого входа -->
<div class="quick-login-modal" id="quickLoginModal" style="display: none; position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.5); z-index: 2000; align-items: center; justify-content: center;">
<div style="background: white; padding: 30px; border-radius: 10px; width: 90%; max-width: 400px; text-align: center;">
<h3 style="margin-bottom: 20px; color: #453227;">
<i class="fas fa-sign-in-alt"></i> Быстрый вход
</h3>
<div style="margin-bottom: 20px;">
<button onclick="quickLogin('admin')" style="width: 100%; padding: 12px; background: #617365; color: white; border: none; border-radius: 4px; margin-bottom: 10px; cursor: pointer;">
<i class="fas fa-user-shield"></i> Войти как Администратор
</button>
<button onclick="quickLogin('user')" style="width: 100%; padding: 12px; background: #28a745; color: white; border: none; border-radius: 4px; cursor: pointer;">
<i class="fas fa-user"></i> Войти как Пользователь
</button>
</div>
<div style="font-size: 12px; color: #666; margin-top: 15px;">
Для полного функционала перейдите на страницу входа
</div>
<div style="margin-top: 20px; display: flex; gap: 10px; justify-content: center;">
<a href="вход.html" class="btn primary-btn" style="padding: 8px 15px; font-size: 14px;">
Полный вход
</a>
<button onclick="hideQuickLogin()" class="btn" style="padding: 8px 15px; font-size: 14px; background: #6c757d; color: white;">
Отмена
</button>
</div>
</div>
</div>
<script>
// Проверка авторизации
function checkAuth(redirectUrl) {
// Просто перенаправляем на страницу с action
window.location.href = 'cite_mebel.php?action=go_to_catalog';
return true;
}
// Проверяем статус при загрузке
$(document).ready(function() {
// Синхронизируем PHP сессию с localStorage
<?php if (isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true): ?>
if (localStorage.getItem('isLoggedIn') !== 'true') {
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('user_email', '<?= addslashes($_SESSION['user_email'] ?? '') ?>');
localStorage.setItem('isAdmin', '<?= isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] ? 'true' : 'false' ?>');
}
<?php endif; ?>
// Блокируем все ссылки на каталог если не авторизован
const isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
if (!isLoggedIn) {
// Находим ВСЕ ссылки на каталог
$('a[href*="catalog"]').each(function() {
const originalHref = $(this).attr('href');
if (originalHref && originalHref.includes('catalog')) {
$(this).attr('href', 'javascript:void(0)');
$(this).addClass('link-disabled');
$(this).click(function(e) {
e.preventDefault();
checkAuth(originalHref);
});
}
});
// Блокируем поиск
$('#searchInput').prop('disabled', true).attr('placeholder', 'Войдите для поиска');
}
});
// Быстрый вход для тестирования (можно удалить в продакшене)
function quickLogin(role) {
let email, password;
if (role === 'admin') {
email = 'admin@aeterna.ru';
password = 'admin123';
} else {
email = 'user@test.com';
password = 'user123';
}
$.ajax({
url: 'login_handler.php',
method: 'POST',
data: {
email: email,
password: password
},
success: function(response) {
try {
const result = JSON.parse(response);
if (result.success) {
// Сохраняем в localStorage
localStorage.setItem('isLoggedIn', 'true');
localStorage.setItem('user_email', email);
localStorage.setItem('isAdmin', (role === 'admin').toString());
alert('Вы вошли как ' + (role === 'admin' ? 'администратор' : 'пользователь'));
location.reload();
}
} catch(e) {
console.error(e);
}
}
});
}
</script>
<script>
$(document).ready(function() {
// Открытие/закрытие меню профиля
$('.user-profile-toggle').click(function(e) {
e.stopPropagation();
$('.user-profile-menu').toggle();
});
// Закрытие меню при клике вне его
$(document).click(function() {
$('.user-profile-menu').hide();
});
// Обработка выхода
$('.logout-link').click(function(e) {
if (!confirm('Вы действительно хотите выйти?')) {
e.preventDefault();
}
});
// Обновляем время входа при загрузке страницы
updateLoginTime();
});
function updateLoginTime() {
// Можно добавить AJAX запрос для обновления времени последнего входа
// или использовать время из сессии
}
</script>
<!-- Кнопки быстрого тестирования -->
<div style="position: fixed; bottom: 20px; right: 20px; z-index: 1000; background: white; padding: 10px; border-radius: 5px; box-shadow: 0 2px 10px rgba(0,0,0,0.1);">
<h4 style="margin: 0 0 10px 0; font-size: 14px; color: #453227;">Быстрый вход:</h4>
<button onclick="window.location.href='профиль.php?quick_login=admin'" style="background: #617365; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin: 5px; width: 100%;">
<i class="fas fa-user-shield"></i> Войти как Админ
</button>
<button onclick="window.location.href='профиль.php?quick_login=user'" style="background: #28a745; color: white; border: none; padding: 8px 15px; border-radius: 4px; cursor: pointer; margin: 5px; width: 100%;">
<i class="fas fa-user"></i> Войти как Пользователь
</button>
</div>
</body>
</html>

View File

@@ -1,51 +1,38 @@
<?php <?php
/**
* Конфигурация приложения
*/
return [ return [
// Название приложения
'name' => 'AETERNA', 'name' => 'AETERNA',
'debug' => getenv('APP_DEBUG') ?: true,
// Режим отладки 'url' => getenv('APP_URL') ?: 'http://localhost:8080',
'debug' => true, 'base_path' => '',
// URL приложения
'url' => 'http://localhost',
// Базовый путь (для Docker)
'base_path' => '/cite_practica',
// Часовой пояс
'timezone' => 'Europe/Moscow', 'timezone' => 'Europe/Moscow',
// Локаль
'locale' => 'ru_RU', 'locale' => 'ru_RU',
// Email администраторов (получают права администратора при регистрации)
'admin_emails' => [ 'admin_emails' => [
'admin@aeterna.ru', 'admin@aeterna.ru',
'administrator@aeterna.ru', 'administrator@aeterna.ru',
'aeterna@mail.ru' 'aeterna@mail.ru'
], ],
// Настройки сессии
'session' => [ 'session' => [
'lifetime' => 120, // минуты 'lifetime' => 120,
'secure' => false, 'secure' => false,
'http_only' => true 'http_only' => true
], ],
// Настройки доставки
'delivery' => [ 'delivery' => [
'default_price' => 2000, 'default_price' => 2000,
'free_from' => 50000, // Бесплатная доставка от этой суммы 'free_from' => 50000,
], ],
// Промокоды
'promo_codes' => [ 'promo_codes' => [
'SALE10' => ['type' => 'percent', 'value' => 10], 'SALE10' => ['type' => 'percent', 'value' => 10],
'FREE' => ['type' => 'free_delivery', 'value' => 0], 'FREE' => ['type' => 'free_delivery', 'value' => 0],
],
'paths' => [
'storage' => 'storage',
'uploads' => 'storage/uploads',
'assets' => 'public/assets',
] ]
]; ];

View File

@@ -1,114 +0,0 @@
// check_auth.js
$(document).ready(function() {
// Проверка авторизации при загрузке страницы
checkAuthStatus();
// Обработка формы входа
$('#loginForm').on('submit', function(e) {
e.preventDefault();
const email = $('#login-email').val();
const password = $('#login-password').val();
const remember = $('#remember').is(':checked');
$.ajax({
url: 'login_handler.php',
method: 'POST',
data: {
email: email,
password: password
},
success: function(response) {
try {
const result = JSON.parse(response);
if (result.success) {
// Сохраняем в localStorage если выбрано "Запомнить меня"
if (remember) {
localStorage.setItem('rememberedEmail', email);
} else {
localStorage.removeItem('rememberedEmail');
}
// Перенаправляем
window.location.href = result.redirect || 'catalog.php';
} else {
showMessage('error', result.message || 'Ошибка авторизации');
}
} catch(e) {
showMessage('error', 'Ошибка обработки ответа');
}
},
error: function() {
showMessage('error', 'Ошибка сервера');
}
});
});
// Проверка статуса авторизации
function checkAuthStatus() {
$.ajax({
url: 'check_auth_status.php',
method: 'GET',
success: function(response) {
try {
const result = JSON.parse(response);
if (result.loggedIn) {
updateUserProfile(result.user);
}
} catch(e) {
console.error('Ошибка проверки авторизации', e);
}
}
});
}
// Обновление профиля пользователя
function updateUserProfile(user) {
// Обновляем шапку, если есть элементы для профиля
if ($('#userEmail').length) {
$('#userEmail').text(user.email);
}
if ($('#userName').length) {
$('#userName').text(user.full_name);
}
}
// Показать сообщение
function showMessage(type, text) {
const $message = $('#' + type + 'Message');
if ($message.length) {
$message.text(text).fadeIn();
setTimeout(() => $message.fadeOut(), 5000);
} else {
alert(text);
}
}
// Проверка авторизации для ссылок
function checkAuth(redirectUrl) {
$.ajax({
url: 'check_auth_status.php',
method: 'GET',
success: function(response) {
try {
const result = JSON.parse(response);
if (result.loggedIn) {
window.location.href = redirectUrl;
} else {
// Показываем модальное окно или перенаправляем на вход
showLoginModal(redirectUrl);
}
} catch(e) {
showLoginModal(redirectUrl);
}
}
});
return false;
}
// Показать модальное окно входа
function showLoginModal(redirectUrl) {
// Можно реализовать модальное окно или перенаправить на страницу входа
window.location.href = 'вход.php?redirect=' + encodeURIComponent(redirectUrl);
}
});

View File

@@ -1,14 +1,17 @@
<?php <?php
/**
* Конфигурация базы данных
*/
return [ return [
'driver' => 'pgsql', 'driver' => getenv('DB_DRIVER') ?: 'pgsql',
'host' => '185.130.224.177', 'host' => getenv('DB_HOST') ?: '185.130.224.177',
'port' => '5481', 'port' => getenv('DB_PORT') ?: '5481',
'database' => 'postgres', 'database' => getenv('DB_DATABASE') ?: 'postgres',
'username' => 'admin', 'username' => getenv('DB_USERNAME') ?: 'admin',
'password' => '38feaad2840ccfda0e71243a6faaecfd', 'password' => getenv('DB_PASSWORD') ?: '38feaad2840ccfda0e71243a6faaecfd',
'charset' => 'utf8', 'charset' => 'utf8',
'options' => [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false,
]
]; ];

View File

@@ -1,46 +1,32 @@
<?php <?php
/**
* Определение маршрутов приложения
*
* @var \App\Core\Router $router
*/
// ========== Главная страница ==========
$router->get('/', 'HomeController', 'index'); $router->get('/', 'HomeController', 'index');
$router->get('/home', 'HomeController', 'index'); $router->get('/home', 'HomeController', 'index');
// ========== Авторизация ==========
$router->get('/login', 'AuthController', 'loginForm'); $router->get('/login', 'AuthController', 'loginForm');
$router->post('/login', 'AuthController', 'login'); $router->post('/login', 'AuthController', 'login');
$router->get('/register', 'AuthController', 'registerForm'); $router->get('/register', 'AuthController', 'registerForm');
$router->post('/register', 'AuthController', 'register'); $router->post('/register', 'AuthController', 'register');
$router->get('/logout', 'AuthController', 'logout'); $router->get('/logout', 'AuthController', 'logout');
// ========== Каталог и товары ==========
$router->get('/catalog', 'ProductController', 'catalog'); $router->get('/catalog', 'ProductController', 'catalog');
$router->get('/product/{id}', 'ProductController', 'show'); $router->get('/product/{id}', 'ProductController', 'show');
// ========== Корзина ==========
$router->get('/cart', 'CartController', 'index'); $router->get('/cart', 'CartController', 'index');
$router->post('/cart/add', 'CartController', 'add'); $router->post('/cart/add', 'CartController', 'add');
$router->post('/cart/update', 'CartController', 'update'); $router->post('/cart/update', 'CartController', 'update');
$router->post('/cart/remove', 'CartController', 'remove'); $router->post('/cart/remove', 'CartController', 'remove');
$router->get('/cart/count', 'CartController', 'count'); $router->get('/cart/count', 'CartController', 'count');
// ========== Заказы ==========
$router->get('/checkout', 'OrderController', 'checkout'); $router->get('/checkout', 'OrderController', 'checkout');
$router->post('/order', 'OrderController', 'create'); $router->post('/order', 'OrderController', 'create');
// ========== Статические страницы ==========
$router->get('/services', 'PageController', 'services'); $router->get('/services', 'PageController', 'services');
$router->get('/delivery', 'PageController', 'delivery'); $router->get('/delivery', 'PageController', 'delivery');
$router->get('/warranty', 'PageController', 'warranty'); $router->get('/warranty', 'PageController', 'warranty');
// ========== Админ-панель ==========
$router->get('/admin', 'AdminController', 'dashboard'); $router->get('/admin', 'AdminController', 'dashboard');
// Управление товарами
$router->get('/admin/products', 'AdminController', 'products'); $router->get('/admin/products', 'AdminController', 'products');
$router->get('/admin/products/add', 'AdminController', 'addProduct'); $router->get('/admin/products/add', 'AdminController', 'addProduct');
$router->post('/admin/products/add', 'AdminController', 'storeProduct'); $router->post('/admin/products/add', 'AdminController', 'storeProduct');
@@ -48,7 +34,6 @@ $router->get('/admin/products/edit/{id}', 'AdminController', 'editProduct');
$router->post('/admin/products/edit/{id}', 'AdminController', 'updateProduct'); $router->post('/admin/products/edit/{id}', 'AdminController', 'updateProduct');
$router->post('/admin/products/delete/{id}', 'AdminController', 'deleteProduct'); $router->post('/admin/products/delete/{id}', 'AdminController', 'deleteProduct');
// Управление категориями
$router->get('/admin/categories', 'AdminController', 'categories'); $router->get('/admin/categories', 'AdminController', 'categories');
$router->get('/admin/categories/add', 'AdminController', 'addCategory'); $router->get('/admin/categories/add', 'AdminController', 'addCategory');
$router->post('/admin/categories/add', 'AdminController', 'storeCategory'); $router->post('/admin/categories/add', 'AdminController', 'storeCategory');
@@ -56,11 +41,8 @@ $router->get('/admin/categories/edit/{id}', 'AdminController', 'editCategory');
$router->post('/admin/categories/edit/{id}', 'AdminController', 'updateCategory'); $router->post('/admin/categories/edit/{id}', 'AdminController', 'updateCategory');
$router->post('/admin/categories/delete/{id}', 'AdminController', 'deleteCategory'); $router->post('/admin/categories/delete/{id}', 'AdminController', 'deleteCategory');
// Управление заказами
$router->get('/admin/orders', 'AdminController', 'orders'); $router->get('/admin/orders', 'AdminController', 'orders');
$router->get('/admin/orders/{id}', 'AdminController', 'orderDetails'); $router->get('/admin/orders/{id}', 'AdminController', 'orderDetails');
$router->post('/admin/orders/{id}/status', 'AdminController', 'updateOrderStatus'); $router->post('/admin/orders/{id}/status', 'AdminController', 'updateOrderStatus');
// Управление пользователями
$router->get('/admin/users', 'AdminController', 'users'); $router->get('/admin/users', 'AdminController', 'users');

View File

@@ -1,55 +0,0 @@
<?php
// debug_db.php
require_once 'config/database.php';
$db = Database::getInstance()->getConnection();
echo "<h2>Проверка базы данных:</h2>";
// Проверка таблиц
$tables = ['users', 'categories', 'products', 'orders', 'order_items', 'cart'];
foreach ($tables as $table) {
try {
$result = $db->query("SELECT COUNT(*) FROM $table")->fetchColumn();
echo "✅ Таблица '$table': $result записей<br>";
} catch (Exception $e) {
echo "❌ Таблица '$table': НЕ СУЩЕСТВУЕТ<br>";
}
}
echo "<h2>Содержимое таблиц:</h2>";
// Показать категории
echo "<h3>Категории:</h3>";
try {
$categories = $db->query("SELECT * FROM categories")->fetchAll();
if (empty($categories)) {
echo "Категорий нет!<br>";
} else {
echo "<table border='1'><tr><th>ID</th><th>Название</th><th>Slug</th><th>Родитель</th></tr>";
foreach ($categories as $cat) {
echo "<tr><td>{$cat['category_id']}</td><td>{$cat['name']}</td><td>{$cat['slug']}</td><td>{$cat['parent_id']}</td></tr>";
}
echo "</table>";
}
} catch (Exception $e) {
echo "Ошибка: " . $e->getMessage();
}
// Показать товары
echo "<h3>Товары:</h3>";
try {
$products = $db->query("SELECT * FROM products")->fetchAll();
if (empty($products)) {
echo "Товаров нет!<br>";
} else {
echo "<table border='1'><tr><th>ID</th><th>Название</th><th>Цена</th><th>Категория</th><th>Статус</th></tr>";
foreach ($products as $product) {
echo "<tr><td>{$product['product_id']}</td><td>{$product['name']}</td><td>{$product['price']}</td><td>{$product['category_id']}</td><td>" . ($product['is_available'] ? 'Активен' : 'Неактивен') . "</td></tr>";
}
echo "</table>";
}
} catch (Exception $e) {
echo "Ошибка: " . $e->getMessage();
}
?>

View File

@@ -11,6 +11,8 @@ services:
environment: environment:
- APACHE_RUN_USER=www-data - APACHE_RUN_USER=www-data
- APACHE_RUN_GROUP=www-data - APACHE_RUN_GROUP=www-data
- APP_ENV=development
- APP_DEBUG=true
restart: unless-stopped restart: unless-stopped
networks: networks:
- aeterna-network - aeterna-network
@@ -18,4 +20,3 @@ services:
networks: networks:
aeterna-network: aeterna-network:
driver: bridge driver: bridge

View File

@@ -1,20 +1,16 @@
#!/bin/bash #!/bin/bash
set -e set -e
# Включаем mod_rewrite a2enmod rewrite headers expires 2>/dev/null || true
a2enmod rewrite 2>/dev/null || true
# Устанавливаем права только на нужные директории, исключая .git
find /var/www/html -maxdepth 1 -type d ! -name '.git' -exec chown -R www-data:www-data {} \; 2>/dev/null || true
find /var/www/html -maxdepth 1 -type f -exec chown www-data:www-data {} \; 2>/dev/null || true
# Устанавливаем права на ключевые директории
chown -R www-data:www-data /var/www/html/app 2>/dev/null || true chown -R www-data:www-data /var/www/html/app 2>/dev/null || true
chown -R www-data:www-data /var/www/html/config 2>/dev/null || true chown -R www-data:www-data /var/www/html/config 2>/dev/null || true
chown -R www-data:www-data /var/www/html/public 2>/dev/null || true chown -R www-data:www-data /var/www/html/public 2>/dev/null || true
chown -R www-data:www-data /var/www/html/uploads 2>/dev/null || true chown -R www-data:www-data /var/www/html/storage 2>/dev/null || true
echo "Apache configured successfully" chmod -R 775 /var/www/html/storage 2>/dev/null || true
echo "AETERNA - Apache configured successfully"
echo "DocumentRoot: /var/www/html/public"
# Запускаем Apache в foreground режиме
exec apache2-foreground exec apache2-foreground

View File

@@ -1,25 +1,29 @@
<VirtualHost *:80> <VirtualHost *:80>
ServerAdmin admin@aeterna.local ServerAdmin admin@aeterna.local
DocumentRoot /var/www/html DocumentRoot /var/www/html/public
ServerName localhost ServerName localhost
<Directory /var/www/html> <Directory /var/www/html/public>
Options Indexes FollowSymLinks Options -Indexes +FollowSymLinks
AllowOverride All AllowOverride All
Require all granted Require all granted
</Directory> </Directory>
# Логирование Alias /uploads /var/www/html/storage/uploads
<Directory /var/www/html/storage/uploads>
Options -Indexes
AllowOverride None
Require all granted
</Directory>
ErrorLog ${APACHE_LOG_DIR}/error.log ErrorLog ${APACHE_LOG_DIR}/error.log
CustomLog ${APACHE_LOG_DIR}/access.log combined CustomLog ${APACHE_LOG_DIR}/access.log combined
# Кодировка по умолчанию
AddDefaultCharset UTF-8 AddDefaultCharset UTF-8
# Типы файлов
AddType text/css .css AddType text/css .css
AddType text/less .less AddType text/less .less
AddType text/javascript .js AddType text/javascript .js
AddType image/svg+xml .svg AddType image/svg+xml .svg
AddType image/webp .webp
</VirtualHost> </VirtualHost>

View File

@@ -1,69 +0,0 @@
<?php
// fix_categories.php
require_once 'config/database.php';
$db = Database::getInstance()->getConnection();
echo "<h2>Исправление проблем с категориями</h2>";
try {
// 1. Удаляем категорию с ID=0 если она есть
$db->exec("DELETE FROM categories WHERE category_id = 0");
// 2. Проверяем, есть ли категории
$catCount = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn();
if ($catCount == 0) {
echo "<p>Добавляем основные категории...</p>";
$db->exec("
INSERT INTO categories (name, slug, description, is_active) VALUES
('Диваны', 'divany', 'Мягкая мебель для гостиной', TRUE),
('Кресла', 'kresla', 'Кресла для гостиной и офиса', TRUE),
('Кровати', 'krovati', 'Мебель для спальни', TRUE),
('Столы', 'stoly', 'Обеденные и рабочие столы', TRUE),
('Стулья', 'stulya', 'Стулья для кухни и офиса', TRUE)
");
echo "<p style='color: green;'>✓ Категории добавлены</p>";
}
// 3. Исправляем товары с category_id = 0 или NULL
$badProducts = $db->query("
SELECT COUNT(*) FROM products
WHERE category_id IS NULL OR category_id = 0 OR
category_id NOT IN (SELECT category_id FROM categories)
")->fetchColumn();
if ($badProducts > 0) {
echo "<p>Исправляем товары с некорректными категориями ($badProducts шт)...</p>";
// Получаем первую категорию
$firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn();
if ($firstCat) {
$db->exec("
UPDATE products
SET category_id = $firstCat
WHERE category_id IS NULL OR category_id = 0 OR
category_id NOT IN (SELECT category_id FROM categories)
");
echo "<p style='color: green;'>✓ Товары исправлены (category_id установлен в $firstCat)</p>";
}
}
// 4. Показываем текущее состояние
echo "<h3>Текущие категории:</h3>";
$cats = $db->query("SELECT category_id, name FROM categories ORDER BY category_id")->fetchAll();
echo "<table border='1' cellpadding='5'>";
echo "<tr><th>ID</th><th>Название</th></tr>";
foreach ($cats as $cat) {
echo "<tr><td>{$cat['category_id']}</td><td>{$cat['name']}</td></tr>";
}
echo "</table>";
echo "<p style='color: green; margin-top: 20px;'>✓ База данных исправлена!</p>";
} catch (PDOException $e) {
echo "<p style='color: red;'>Ошибка: " . $e->getMessage() . "</p>";
}
?>

View File

@@ -1,89 +0,0 @@
<?php
// fix_database.php
require_once 'config/database.php';
$db = Database::getInstance()->getConnection();
echo "<h2>Исправление проблем с базой данных</h2>";
try {
// 1. Проверяем есть ли категории
$stmt = $db->query("SELECT COUNT(*) FROM categories");
$cat_count = $stmt->fetchColumn();
if ($cat_count == 0) {
echo "<p>Добавляем тестовые категории...</p>";
$db->exec("
INSERT INTO categories (name, slug, description) VALUES
('Диваны', 'divany', 'Мягкая мебель для гостиной'),
('Кресла', 'kresla', 'Кресла для гостиной и офиса'),
('Кровати', 'krovati', 'Мебель для спальни')
");
echo "<p style='color: green;'>✓ Категории добавлены</p>";
}
// 2. Проверяем товары с некорректными category_id
$stmt = $db->query("
SELECT COUNT(*) as bad_count
FROM products
WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories)
");
$bad_count = $stmt->fetchColumn();
if ($bad_count > 0) {
echo "<p>Исправляем товары с некорректными категориями ($bad_count шт)...</p>";
// Устанавливаем первую доступную категорию
$stmt = $db->query("SELECT category_id FROM categories LIMIT 1");
$first_cat = $stmt->fetchColumn();
if ($first_cat) {
$db->exec("
UPDATE products
SET category_id = $first_cat
WHERE category_id IS NULL OR category_id NOT IN (SELECT category_id FROM categories)
");
echo "<p style='color: green;'>✓ Товары исправлены (установлена категория ID: $first_cat)</p>";
}
}
// 3. Показываем текущее состояние
echo "<h3>Текущее состояние:</h3>";
// Категории
$stmt = $db->query("SELECT category_id, name FROM categories ORDER BY category_id");
echo "<p><strong>Категории:</strong></p><ul>";
while ($row = $stmt->fetch()) {
echo "<li>ID: {$row['category_id']} - {$row['name']}</li>";
}
echo "</ul>";
// Товары
$stmt = $db->query("
SELECT p.product_id, p.name, p.category_id, c.name as cat_name
FROM products p
LEFT JOIN categories c ON p.category_id = c.category_id
ORDER BY p.product_id
");
echo "<p><strong>Товары:</strong></p>";
echo "<table border='1' cellpadding='5'>";
echo "<tr><th>ID</th><th>Название</th><th>Категория ID</th><th>Категория</th></tr>";
while ($row = $stmt->fetch()) {
echo "<tr>";
echo "<td>{$row['product_id']}</td>";
echo "<td>" . htmlspecialchars($row['name']) . "</td>";
echo "<td>" . ($row['category_id'] ?: 'NULL') . "</td>";
echo "<td>" . ($row['cat_name'] ?: 'Без категории') . "</td>";
echo "</tr>";
}
echo "</table>";
echo "<p style='color: green; margin-top: 20px;'>✓ База данных исправлена!</p>";
} catch (PDOException $e) {
echo "<p style='color: red;'>Ошибка: " . $e->getMessage() . "</p>";
}
?>

View File

@@ -1,62 +0,0 @@
<?php
// get_cart.php
session_start();
require_once 'config/database.php';
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
echo json_encode(['success' => false, 'message' => 'Требуется авторизация']);
exit();
}
$user_id = $_SESSION['user_id'] ?? 0;
if ($user_id == 0) {
echo json_encode(['success' => false, 'message' => 'Пользователь не найден']);
exit();
}
$db = Database::getInstance()->getConnection();
try {
// Получаем корзину из БД
$stmt = $db->prepare("
SELECT
c.cart_id,
c.product_id,
c.quantity,
p.name,
p.price,
p.image_url,
p.stock_quantity
FROM cart c
JOIN products p ON c.product_id = p.product_id
WHERE c.user_id = ? AND p.is_available = TRUE
ORDER BY c.created_at DESC
");
$stmt->execute([$user_id]);
$cart_items = $stmt->fetchAll();
// Обновляем сессию
$_SESSION['cart'] = [];
foreach ($cart_items as $item) {
$_SESSION['cart'][$item['product_id']] = [
'quantity' => $item['quantity'],
'name' => $item['name'],
'price' => $item['price'],
'added_at' => time()
];
}
echo json_encode([
'success' => true,
'cart_items' => $cart_items,
'total_items' => count($cart_items)
]);
} catch (PDOException $e) {
echo json_encode([
'success' => false,
'message' => 'Ошибка базы данных: ' . $e->getMessage()
]);
}
?>

View File

@@ -1,22 +0,0 @@
<?php
// get_cart_count.php
session_start();
require_once 'config/database.php';
if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) {
echo json_encode(['success' => false, 'cart_count' => 0]);
exit();
}
$user_id = $_SESSION['user_id'] ?? 0;
$db = Database::getInstance()->getConnection();
try {
$stmt = $db->prepare("SELECT SUM(quantity) as total FROM cart WHERE user_id = ?");
$stmt->execute([$user_id]);
$cart_count = $stmt->fetchColumn() ?: 0;
echo json_encode(['success' => true, 'cart_count' => $cart_count]);
} catch (PDOException $e) {
echo json_encode(['success' => false, 'cart_count' => 0]);
}

View File

@@ -1,33 +0,0 @@
<?php
session_start();
require_once 'config/database.php';
// Проверяем авторизацию администратора
if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
echo json_encode(['success' => false, 'message' => 'Доступ запрещен']);
exit();
}
if (!isset($_GET['id'])) {
echo json_encode(['success' => false, 'message' => 'ID не указан']);
exit();
}
try {
$db = Database::getInstance()->getConnection();
$product_id = $_GET['id'];
$stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?");
$stmt->execute([$product_id]);
$product = $stmt->fetch();
if ($product) {
echo json_encode($product);
} else {
echo json_encode(['success' => false, 'message' => 'Товар не найден']);
}
} catch (PDOException $e) {
echo json_encode(['success' => false, 'message' => 'Ошибка базы данных: ' . $e->getMessage()]);
}
?>

View File

@@ -1,142 +0,0 @@
<?php
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
$isLoggedIn = isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true;
$isAdmin = isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true;
$userEmail = $_SESSION['user_email'] ?? '';
$fullName = $_SESSION['full_name'] ?? $userEmail;
?>
<!-- Стандартный header для всех страниц -->
<header class="header">
<div class="header__top">
<div class="container header__top-content">
<div class="logo">AETERNA</div>
<div class="search-catalog">
<div class="catalog-dropdown">
Все категории <span>&#9660;</span>
<div class="catalog-dropdown__menu">
<ul>
<li><a href="catalog.php">Все товары</a></li>
<li><a href="catalog.php?category=1">Мягкая мебель</a></li>
<li><a href="catalog.php?category=2">Диваны</a></li>
<li><a href="catalog.php?category=3">Кресла</a></li>
<li><a href="catalog.php?category=4">Спальня</a></li>
<li><a href="catalog.php?category=5">Кровати</a></li>
</ul>
</div>
</div>
<div class="search-box">
<input type="text" placeholder="Поиск товаров" id="searchInput">
<span class="search-icon"><i class="fas fa-search"></i></span>
</div>
</div>
<div class="header__icons--top">
<?php if ($isLoggedIn): ?>
<!-- Иконка корзины -->
<a href="оформлениеаказа.php" class="icon cart-icon">
<i class="fas fa-shopping-cart"></i>
<span class="cart-count">0</span>
</a>
<!-- Блок профиля -->
<div class="user-profile-dropdown">
<div class="user-profile-toggle">
<div class="user-avatar">
<?= !empty($userEmail) ? strtoupper(substr($userEmail, 0, 1)) : 'U' ?>
</div>
<div class="user-info">
<div class="user-email"><?= htmlspecialchars($userEmail) ?></div>
<div class="user-status <?= $isAdmin ? 'admin' : 'user' ?>">
<?= $isAdmin ? 'Админ' : 'Пользователь' ?>
</div>
</div>
<i class="fas fa-chevron-down dropdown-arrow"></i>
</div>
<div class="user-profile-menu">
<div class="user-profile-header">
<div class="user-profile-name">
<i class="fas fa-user"></i> <?= htmlspecialchars($fullName) ?>
</div>
<div class="user-profile-details">
<small><i class="far fa-envelope"></i> <?= htmlspecialchars($userEmail) ?></small>
<?php if (isset($_SESSION['login_time'])): ?>
<small><i class="far fa-clock"></i> Вошел: <?= date('d.m.Y H:i', $_SESSION['login_time']) ?></small>
<?php endif; ?>
</div>
</div>
<ul class="user-profile-links">
<li>
<a href="профиль.php">
<i class="fas fa-user-cog"></i>
<span>Настройки профиля</span>
</a>
</li>
<li>
<a href="оформлениеаказа.php">
<i class="fas fa-shopping-bag"></i>
<span>Мои заказы</span>
</a>
</li>
<?php if ($isAdmin): ?>
<li>
<a href="admin_panel.php">
<i class="fas fa-user-shield"></i>
<span>Панель администратора</span>
</a>
</li>
<?php endif; ?>
<li class="logout-item">
<a href="logout.php" class="logout-link">
<i class="fas fa-sign-out-alt"></i>
<span>Выйти из аккаунта</span>
</a>
</li>
</ul>
</div>
</div>
<?php else: ?>
<!-- Если не авторизован -->
<a href="вход.php" class="icon">
<i class="far fa-user"></i>
</a>
<a href="вход.php" style="font-size: 12px; color: #666; margin-left: 5px;">
Войти
</a>
<?php endif; ?>
</div>
</div>
</div>
<div class="header__bottom">
<div class="container header__bottom-content">
<div class="catalog-menu">
<a href="catalog.php" class="catalog-link">
<div class="catalog-icon">
<span class="line"></span>
<span class="line"></span>
<span class="line"></span>
</div>
<span class="catalog-lines">☰</span>
Каталог
</a>
</div>
<nav class="nav">
<ul class="nav-list">
<li><a href="cite_mebel.php">Главная</a></li>
<li><a href="услуги.php">Услуги</a></li>
<li><a href="Доставка.php">Доставка и оплата</a></li>
<li><a href="Гарантия.php">Гарантия</a></li>
<li><a href="#footer">Контакты</a></li>
</ul>
</nav>
<div class="header-phone">+7(912)999-12-23</div>
</div>
</div>
</header>

View File

@@ -1,53 +0,0 @@
<?php
// image_upload.php
session_start();
// Проверка прав администратора
if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) {
echo json_encode(['success' => false, 'message' => 'Доступ запрещен']);
exit();
}
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) {
$uploadDir = 'uploads/products/';
// Создаем директорию если не существует
if (!file_exists($uploadDir)) {
mkdir($uploadDir, 0777, true);
}
$allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
$maxSize = 5 * 1024 * 1024; // 5MB
$file = $_FILES['image'];
// Проверка типа файла
if (!in_array($file['type'], $allowedTypes)) {
echo json_encode(['success' => false, 'message' => 'Допустимые форматы: JPEG, PNG, GIF, WebP']);
exit();
}
// Проверка размера
if ($file['size'] > $maxSize) {
echo json_encode(['success' => false, 'message' => 'Максимальный размер файла: 5MB']);
exit();
}
// Генерируем уникальное имя
$extension = pathinfo($file['name'], PATHINFO_EXTENSION);
$fileName = 'product_' . time() . '_' . rand(1000, 9999) . '.' . $extension;
$filePath = $uploadDir . $fileName;
if (move_uploaded_file($file['tmp_name'], $filePath)) {
echo json_encode([
'success' => true,
'url' => $filePath,
'name' => $fileName
]);
} else {
echo json_encode(['success' => false, 'message' => 'Ошибка загрузки файла']);
}
} else {
echo json_encode(['success' => false, 'message' => 'Файл не получен']);
}
?>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

BIN
img/2.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

BIN
img/3.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 51 KiB

BIN
img/4.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

BIN
img/6.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 42 KiB

BIN
img/7.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 36 KiB

BIN
img/8.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 109 KiB

BIN
img/9.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 179 KiB

View File

@@ -1,62 +0,0 @@
.error-message {
color: #ff0000;
font-size: 12px;
margin-top: 5px;
display: none;
}
.form__input.error {
border-color: #ff0000;
}
.form__group {
position: relative;
margin-bottom: 15px;
}
/* Стили для сообщений внизу страницы */
.page-messages {
position: fixed;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
z-index: 1000;
width: 90%;
max-width: 500px;
}
.message {
padding: 15px;
margin: 10px 0;
border-radius: 5px;
text-align: center;
font-weight: bold;
display: none;
}
.message.error {
background-color: #ffebee;
color: #c62828;
border: 1px solid #ffcdd2;
}
.message.success {
background-color: #e8f5e9;
color: #453227;
border: 1px solid #c8e6c9;
}
.message.warning {
background-color: #fff3e0;
color: #ef6c00;
border: 1px solid #ffe0b2;
}
.privacy-error {
color: #ff0000;
font-size: 12px;
margin-top: 5px;
display: none;
text-align: center;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 127 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 228 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 294 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 187 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 188 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 110 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 106 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 306 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 151 KiB

View File

@@ -1,48 +0,0 @@
<?php
session_start();
require_once 'config/database.php';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = $_POST['email'] ?? '';
$password = $_POST['password'] ?? '';
$db = Database::getInstance()->getConnection();
try {
// Проверяем пользователя
$stmt = $db->prepare("
SELECT user_id, email, password_hash, full_name
FROM users
WHERE email = ? AND is_active = TRUE
");
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
// Сохраняем в сессию
$_SESSION['user_id'] = $user['user_id'];
$_SESSION['user_email'] = $user['email'];
$_SESSION['full_name'] = $user['full_name'];
$_SESSION['isLoggedIn'] = true;
$_SESSION['login_time'] = time();
// Обновляем время последнего входа
$update_stmt = $db->prepare("
UPDATE users
SET updated_at = CURRENT_TIMESTAMP
WHERE user_id = ?
");
$update_stmt->execute([$user['user_id']]);
header('Location: catalog.php');
exit();
} else {
header('Location: вход.php?error=invalid_credentials');
exit();
}
} catch (PDOException $e) {
header('Location: вход.php?error=db_error');
exit();
}
}
?>

Some files were not shown because too many files have changed in this diff Show More