From 4a8d4f8c3f53d6182e1ee0c01772d6ed11da5741 Mon Sep 17 00:00:00 2001 From: "kirill.khorkov" Date: Sat, 3 Jan 2026 18:59:56 +0300 Subject: [PATCH] Fix LESS import error and refactor project structure --- init_db.php | 122 - .gitignore | 63 +- .htaccess | 29 - Dockerfile | 19 +- README.md | 171 + add_to_cart.php | 116 - admin_actions.php | 170 - admin_panel.php | 772 ---- app/Controllers/AdminController.php | 63 - app/Controllers/AuthController.php | 30 - app/Controllers/CartController.php | 24 - app/Controllers/HomeController.php | 7 - app/Controllers/OrderController.php | 12 - app/Controllers/PageController.php | 13 - app/Controllers/ProductController.php | 13 - app/Core/App.php | 78 +- app/Core/Controller.php | 46 - app/Core/Database.php | 27 +- app/Core/Model.php | 37 - app/Core/Router.php | 38 - app/Core/View.php | 52 - app/Models/Cart.php | 34 - app/Models/Category.php | 36 - app/Models/Order.php | 37 - app/Models/Product.php | 45 - app/Models/User.php | 43 +- app/Views/admin/categories/form.php | 4 +- app/Views/admin/categories/index.php | 6 +- app/Views/admin/dashboard.php | 8 +- app/Views/admin/orders/details.php | 6 +- app/Views/admin/orders/index.php | 2 +- app/Views/admin/products/form.php | 4 +- app/Views/admin/products/index.php | 14 +- app/Views/auth/login.php | 7 +- app/Views/auth/register.php | 12 +- app/Views/cart/checkout.php | 61 +- app/Views/errors/404.php | 4 +- app/Views/errors/500.php | 2 +- app/Views/home/index.php | 31 +- app/Views/layouts/admin.php | 15 +- app/Views/layouts/main.php | 9 +- app/Views/pages/delivery.php | 2 +- app/Views/pages/services.php | 2 +- app/Views/pages/warranty.php | 2 +- app/Views/partials/footer.php | 13 +- app/Views/partials/header.php | 39 +- app/Views/products/catalog.php | 35 +- app/Views/products/show.php | 41 +- catalog.php | 1382 ------- catalog_admin.php | 924 ----- catalog_admin_action.php | 111 - check_admin.php | 17 - check_auth_status.php | 21 - check_categories_table.php | 51 - cite_mebel.php | 762 ---- config/app.php | 35 +- config/check_auth.js | 114 - config/database.php | 21 +- config/routes.php | 18 - debug_db.php | 55 - docker-compose.yml | 3 +- docker/apache/entrypoint.sh | 16 +- docker/apache/vhosts.conf | 18 +- fix_categories.php | 69 - fix_database.php | 89 - get_cart.php | 62 - get_cart_count.php | 22 - get_product.php | 33 - header_common.php | 142 - image_upload.php | 53 - img/1_1.jpg | Bin 10919 -> 0 bytes img/2.jpg | Bin 33595 -> 0 bytes img/2_2.jpg | Bin 42754 -> 0 bytes img/3.jpg | Bin 86902 -> 0 bytes img/3_3.jpg | Bin 52093 -> 0 bytes img/4.jpg | Bin 53232 -> 0 bytes img/5_5.jpg | Bin 15321 -> 0 bytes img/6.jpg | Bin 140502 -> 0 bytes img/6_6.jpg | Bin 43107 -> 0 bytes img/7.jpg | Bin 112947 -> 0 bytes img/7_7.jpg | Bin 36355 -> 0 bytes img/8.jpg | Bin 111255 -> 0 bytes img/9.jpg | Bin 183436 -> 0 bytes img/стили_оформления.css | 62 - img2/1.jpg | Bin 79605 -> 0 bytes img2/5.jpg | Bin 92629 -> 0 bytes img2/9_9.jpg | Bin 16518 -> 0 bytes img2/chair.PNG | Bin 147307 -> 0 bytes img2/диван.jpg | Bin 152051 -> 0 bytes img2/диван_1.jpg | Bin 129876 -> 0 bytes img2/кресло.jpg | Bin 233128 -> 0 bytes img2/кресло_1.jpg | Bin 301392 -> 0 bytes img2/слайдер_1.jpg | Bin 191170 -> 0 bytes img2/слайдер_2.jpg | Bin 192118 -> 0 bytes img2/слайдер_3.jpg | Bin 112678 -> 0 bytes img2/слайдер_4.jpg | Bin 108760 -> 0 bytes img2/слайдер_5.jpg | Bin 85078 -> 0 bytes img2/слайдер_6.jpg | Bin 313272 -> 0 bytes img2/спальня.jpg | Bin 154287 -> 0 bytes login.php | 48 - login_handler.php | 66 - logout.php | 22 - mixins.less | 85 - print_order.php | 208 -- process_order.php | 134 - product_modern.php | 350 -- product_page.php | 492 --- public/.htaccess | 46 +- .../products => public/assets/css}/.gitkeep | 0 {img2 => public/assets/images}/1 — копия.jpg | Bin {img => public/assets/images}/1.jpg | Bin {img2 => public/assets/images}/100.jpg | Bin {img2 => public/assets/images}/11.jpg | Bin {img2 => public/assets/images}/111.jpg | Bin {img2 => public/assets/images}/11_1.png | Bin {img2 => public/assets/images}/1_2.jpg | Bin {img2 => public/assets/images}/1_2.png | Bin {img2 => public/assets/images}/22.jpg | Bin {img2 => public/assets/images}/25.jpg | Bin {img2 => public/assets/images}/2_2.jpg | Bin {img2 => public/assets/images}/2_2.png | Bin {img2 => public/assets/images}/3.jpg | Bin {img2 => public/assets/images}/3_3.png | Bin {img2 => public/assets/images}/4.jpg | Bin {img2 => public/assets/images}/44.jpg | Bin {img2 => public/assets/images}/444 | Bin {img2 => public/assets/images}/444 (1).png | Bin {img2 => public/assets/images}/444.jpg | Bin {img2 => public/assets/images}/444.png | Bin {img2 => public/assets/images}/4_1.jpg | Bin {img => public/assets/images}/5.jpg | Bin {img2 => public/assets/images}/5_5.png | Bin {img2 => public/assets/images}/6.jpg | Bin {img2 => public/assets/images}/6_6.png | Bin {img2 => public/assets/images}/7.jpg | Bin {img2 => public/assets/images}/77.jpg | Bin {img2 => public/assets/images}/777 (1).png | Bin {img2 => public/assets/images}/777.jpg | Bin {img2 => public/assets/images}/777.png | Bin {img2 => public/assets/images}/7_7.png | Bin {img2 => public/assets/images}/8.jpg | Bin {img2 => public/assets/images}/88.jpg | Bin {img2 => public/assets/images}/888 (1).png | Bin {img2 => public/assets/images}/888.jpg | Bin {img2 => public/assets/images}/888.png | Bin {img2 => public/assets/images}/8_8.png | Bin {img2 => public/assets/images}/9.jpg | Bin {img2 => public/assets/images}/99.jpg | Bin {img2 => public/assets/images}/99.png | Bin {img2 => public/assets/images}/99_1.jpg | Bin {img2 => public/assets/images}/99_2.jpg | Bin {img2 => public/assets/images}/99_3.png | Bin {img => public/assets/images}/9_9.jpg | Bin {img2 => public/assets/images}/9_9.png | Bin {img2 => public/assets/images}/black.png | Bin {img2 => public/assets/images}/black1.png | Bin {img2 => public/assets/images}/black2.png | Bin {img2 => public/assets/images}/brown.png | Bin {img2 => public/assets/images}/brown1.png | Bin {img2 => public/assets/images}/brown2.png | Bin {img => public/assets/images}/chair.PNG | Bin {img2 => public/assets/images}/gray.png | Bin {img2 => public/assets/images}/gray1.png | Bin {img2 => public/assets/images}/gray2.png | Bin {img => public/assets/images}/диван.jpg | Bin {img => public/assets/images}/диван_1.jpg | Bin {img => public/assets/images}/кресло.jpg | Bin {img => public/assets/images}/кресло_1.jpg | Bin {img => public/assets/images}/слайдер_1.jpg | Bin {img => public/assets/images}/слайдер_2.jpg | Bin {img => public/assets/images}/слайдер_3.jpg | Bin {img => public/assets/images}/слайдер_4.jpg | Bin {img => public/assets/images}/слайдер_5.jpg | Bin {img => public/assets/images}/слайдер_6.jpg | Bin {img => public/assets/images}/спальня.jpg | Bin public/assets/js/.gitkeep | 0 public/index.php | 11 +- public/mixins.less | 3 - public/style_for_cite.less | 60 +- public/uploads | 1 + register.php | 87 - register_handler.php | 182 - storage/uploads/.gitkeep | 0 style_for_cite.less | 3178 ----------------- test_add_simple.php | 131 - tests/DatabaseTest.php | 93 + tests/RegistrationFormTest.php | 77 + tests/SessionTest.php | 82 + tests/TestRunner.php | 80 + tests/UserRegistrationTest.php | 70 + update_cart.php | 31 - Гарантия.php | 206 -- Доставка.php | 174 - вход.php | 305 -- оформление_заказа.php | 484 --- оформление_заказа_jquery.js | 346 -- профиль.php | 778 ---- профиль_jquery.js | 384 -- стили_оформления.less | 142 - страница_товара.php | 237 -- услуги.php | 113 - 201 files changed, 891 insertions(+), 14311 deletions(-) delete mode 100644 init_db.php delete mode 100644 .htaccess create mode 100644 README.md delete mode 100644 add_to_cart.php delete mode 100644 admin_actions.php delete mode 100644 admin_panel.php delete mode 100644 catalog.php delete mode 100644 catalog_admin.php delete mode 100644 catalog_admin_action.php delete mode 100644 check_admin.php delete mode 100644 check_auth_status.php delete mode 100644 check_categories_table.php delete mode 100644 cite_mebel.php delete mode 100644 config/check_auth.js delete mode 100644 debug_db.php delete mode 100644 fix_categories.php delete mode 100644 fix_database.php delete mode 100644 get_cart.php delete mode 100644 get_cart_count.php delete mode 100644 get_product.php delete mode 100644 header_common.php delete mode 100644 image_upload.php delete mode 100644 img/1_1.jpg delete mode 100644 img/2.jpg delete mode 100644 img/2_2.jpg delete mode 100644 img/3.jpg delete mode 100644 img/3_3.jpg delete mode 100644 img/4.jpg delete mode 100644 img/5_5.jpg delete mode 100644 img/6.jpg delete mode 100644 img/6_6.jpg delete mode 100644 img/7.jpg delete mode 100644 img/7_7.jpg delete mode 100644 img/8.jpg delete mode 100644 img/9.jpg delete mode 100644 img/стили_оформления.css delete mode 100644 img2/1.jpg delete mode 100644 img2/5.jpg delete mode 100644 img2/9_9.jpg delete mode 100644 img2/chair.PNG delete mode 100644 img2/диван.jpg delete mode 100644 img2/диван_1.jpg delete mode 100644 img2/кресло.jpg delete mode 100644 img2/кресло_1.jpg delete mode 100644 img2/слайдер_1.jpg delete mode 100644 img2/слайдер_2.jpg delete mode 100644 img2/слайдер_3.jpg delete mode 100644 img2/слайдер_4.jpg delete mode 100644 img2/слайдер_5.jpg delete mode 100644 img2/слайдер_6.jpg delete mode 100644 img2/спальня.jpg delete mode 100644 login.php delete mode 100644 login_handler.php delete mode 100644 logout.php delete mode 100644 mixins.less delete mode 100644 print_order.php delete mode 100644 process_order.php delete mode 100644 product_modern.php delete mode 100644 product_page.php rename {uploads/products => public/assets/css}/.gitkeep (100%) rename {img2 => public/assets/images}/1 — копия.jpg (100%) rename {img => public/assets/images}/1.jpg (100%) rename {img2 => public/assets/images}/100.jpg (100%) rename {img2 => public/assets/images}/11.jpg (100%) rename {img2 => public/assets/images}/111.jpg (100%) rename {img2 => public/assets/images}/11_1.png (100%) rename {img2 => public/assets/images}/1_2.jpg (100%) rename {img2 => public/assets/images}/1_2.png (100%) rename {img2 => public/assets/images}/22.jpg (100%) rename {img2 => public/assets/images}/25.jpg (100%) rename {img2 => public/assets/images}/2_2.jpg (100%) rename {img2 => public/assets/images}/2_2.png (100%) rename {img2 => public/assets/images}/3.jpg (100%) rename {img2 => public/assets/images}/3_3.png (100%) rename {img2 => public/assets/images}/4.jpg (100%) rename {img2 => public/assets/images}/44.jpg (100%) rename {img2 => public/assets/images}/444 (100%) rename {img2 => public/assets/images}/444 (1).png (100%) rename {img2 => public/assets/images}/444.jpg (100%) rename {img2 => public/assets/images}/444.png (100%) rename {img2 => public/assets/images}/4_1.jpg (100%) rename {img => public/assets/images}/5.jpg (100%) rename {img2 => public/assets/images}/5_5.png (100%) rename {img2 => public/assets/images}/6.jpg (100%) rename {img2 => public/assets/images}/6_6.png (100%) rename {img2 => public/assets/images}/7.jpg (100%) rename {img2 => public/assets/images}/77.jpg (100%) rename {img2 => public/assets/images}/777 (1).png (100%) rename {img2 => public/assets/images}/777.jpg (100%) rename {img2 => public/assets/images}/777.png (100%) rename {img2 => public/assets/images}/7_7.png (100%) rename {img2 => public/assets/images}/8.jpg (100%) rename {img2 => public/assets/images}/88.jpg (100%) rename {img2 => public/assets/images}/888 (1).png (100%) rename {img2 => public/assets/images}/888.jpg (100%) rename {img2 => public/assets/images}/888.png (100%) rename {img2 => public/assets/images}/8_8.png (100%) rename {img2 => public/assets/images}/9.jpg (100%) rename {img2 => public/assets/images}/99.jpg (100%) rename {img2 => public/assets/images}/99.png (100%) rename {img2 => public/assets/images}/99_1.jpg (100%) rename {img2 => public/assets/images}/99_2.jpg (100%) rename {img2 => public/assets/images}/99_3.png (100%) rename {img => public/assets/images}/9_9.jpg (100%) rename {img2 => public/assets/images}/9_9.png (100%) rename {img2 => public/assets/images}/black.png (100%) rename {img2 => public/assets/images}/black1.png (100%) rename {img2 => public/assets/images}/black2.png (100%) rename {img2 => public/assets/images}/brown.png (100%) rename {img2 => public/assets/images}/brown1.png (100%) rename {img2 => public/assets/images}/brown2.png (100%) rename {img => public/assets/images}/chair.PNG (100%) rename {img2 => public/assets/images}/gray.png (100%) rename {img2 => public/assets/images}/gray1.png (100%) rename {img2 => public/assets/images}/gray2.png (100%) rename {img => public/assets/images}/диван.jpg (100%) rename {img => public/assets/images}/диван_1.jpg (100%) rename {img => public/assets/images}/кресло.jpg (100%) rename {img => public/assets/images}/кресло_1.jpg (100%) rename {img => public/assets/images}/слайдер_1.jpg (100%) rename {img => public/assets/images}/слайдер_2.jpg (100%) rename {img => public/assets/images}/слайдер_3.jpg (100%) rename {img => public/assets/images}/слайдер_4.jpg (100%) rename {img => public/assets/images}/слайдер_5.jpg (100%) rename {img => public/assets/images}/слайдер_6.jpg (100%) rename {img => public/assets/images}/спальня.jpg (100%) create mode 100644 public/assets/js/.gitkeep create mode 120000 public/uploads delete mode 100644 register.php delete mode 100644 register_handler.php create mode 100755 storage/uploads/.gitkeep delete mode 100644 style_for_cite.less delete mode 100644 test_add_simple.php create mode 100644 tests/DatabaseTest.php create mode 100644 tests/RegistrationFormTest.php create mode 100644 tests/SessionTest.php create mode 100644 tests/TestRunner.php create mode 100644 tests/UserRegistrationTest.php delete mode 100644 update_cart.php delete mode 100644 Гарантия.php delete mode 100644 Доставка.php delete mode 100644 вход.php delete mode 100644 оформление_заказа.php delete mode 100644 оформление_заказа_jquery.js delete mode 100644 профиль.php delete mode 100644 профиль_jquery.js delete mode 100644 стили_оформления.less delete mode 100644 страница_товара.php delete mode 100644 услуги.php diff --git a/ init_db.php b/ init_db.php deleted file mode 100644 index 901a438..0000000 --- a/ init_db.php +++ /dev/null @@ -1,122 +0,0 @@ -getConnection(); - -// Создаем таблицы, если они не существуют -$tables = [ - 'users' => " - CREATE TABLE IF NOT EXISTS users ( - user_id SERIAL PRIMARY KEY, - email VARCHAR(255) UNIQUE NOT NULL, - password_hash VARCHAR(255) NOT NULL, - full_name VARCHAR(100) NOT NULL, - phone VARCHAR(20), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - is_active BOOLEAN DEFAULT TRUE, - is_admin BOOLEAN DEFAULT FALSE - ) - ", - - 'categories' => " - CREATE TABLE IF NOT EXISTS categories ( - category_id SERIAL PRIMARY KEY, - name VARCHAR(100) NOT NULL, - slug VARCHAR(100) UNIQUE NOT NULL, - parent_id INTEGER REFERENCES categories(category_id), - description TEXT, - sort_order INTEGER DEFAULT 0, - is_active BOOLEAN DEFAULT TRUE - ) - ", - - 'products' => " - CREATE TABLE IF NOT EXISTS products ( - product_id SERIAL PRIMARY KEY, - category_id INTEGER REFERENCES categories(category_id), - name VARCHAR(200) NOT NULL, - slug VARCHAR(200) UNIQUE NOT NULL, - description TEXT, - price DECIMAL(10, 2) NOT NULL, - old_price DECIMAL(10, 2), - sku VARCHAR(50) UNIQUE, - stock_quantity INTEGER DEFAULT 0, - is_available BOOLEAN DEFAULT TRUE, - is_featured BOOLEAN DEFAULT FALSE, - rating DECIMAL(3, 2) DEFAULT 0, - review_count INTEGER DEFAULT 0, - image_url VARCHAR(500), - created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP - ) - " -]; - -foreach ($tables_to_create as $table_name => $sql) { - try { - $db->exec($sql); - echo "Таблица '$table_name' создана/проверена
"; - } catch (PDOException $e) { - echo "Ошибка создания таблицы '$table_name': " . $e->getMessage() . "
"; - } -} - -// Добавляем тестовые данные -// Проверяем, есть ли уже категории -$check_categories = $db->query("SELECT COUNT(*) FROM categories")->fetchColumn(); - -if ($check_categories == 0) { - // Добавляем категории - $categories = [ - ['Мягкая мебель', 'myagkaya-mebel', NULL, 'Диваны, кресла, пуфы'], - ['Диваны', 'divany', 1, 'Прямые и угловые диваны'], - ['Кресла', 'kresla', 1, 'Кресла для гостиной и офиса'], - ['Спальня', 'spalnya', NULL, 'Кровати, тумбы, комоды'], - ['Кровати', 'krovati', 4, 'Односпальные и двуспальные кровати'] - ]; - - foreach ($categories as $category) { - $stmt = $db->prepare("INSERT INTO categories (name, slug, parent_id, description) VALUES (?, ?, ?, ?)"); - $stmt->execute($category); - } - echo "Добавлены категории
"; -} - -// Проверяем, есть ли уже товары -$check_products = $db->query("SELECT COUNT(*) FROM products")->fetchColumn(); - -if ($check_products == 0) { - // Добавляем товары - $products = [ - [2, 'Диван VELVET', 'divan-velvet', 'Прямой диван с тканевой обивкой', 45999, 54999, 'DIV-VEL-001', 10], - [2, 'Диван MODERN', 'divan-modern', 'Угловой диван с кожаной обивкой', 78999, 89999, 'DIV-MOD-002', 5], - [3, 'Кресло OPPORTUNITY', 'kreslo-opportunity', 'Кресло с деревянными ножками', 16999, 19999, 'KRES-OPP-001', 15], - [3, 'Кресло GOLDEN', 'kreslo-golden', 'Золотистое кресло для гостиной', 19999, 23999, 'KRES-GOL-002', 8], - [5, 'Кровать CLASSIC', 'krovat-classic', 'Двуспальная кровать из массива дуба', 64999, 74999, 'KROV-CLA-001', 3] - ]; - - foreach ($products as $product) { - $stmt = $db->prepare("INSERT INTO products (category_id, name, slug, description, price, old_price, sku, stock_quantity) - VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); - $stmt->execute($product); - } - echo "Добавлены товары
"; -} - -// Проверяем, есть ли администратор -$check_admin = $db->prepare("SELECT COUNT(*) FROM users WHERE email = ?"); -$check_admin->execute(['admin@aeterna.ru']); - -if ($check_admin->fetchColumn() == 0) { - // Добавляем администратора (пароль: admin123) - $admin_password = password_hash('admin123', PASSWORD_DEFAULT); - $stmt = $db->prepare("INSERT INTO users (email, password_hash, full_name, phone, is_admin) - VALUES (?, ?, ?, ?, ?)"); - $stmt->execute(['admin@aeterna.ru', $admin_password, 'Администратор AETERNA', '+79129991223', true]); - echo "Добавлен администратор (email: admin@aeterna.ru, пароль: admin123)
"; -} - -echo "

База данных успешно инициализирована!

"; -echo "Перейти в каталог"; -?> \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9c84dff..9952a9f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,49 +1,46 @@ -# IDE и редакторы +# Dependencies +/vendor/ +/node_modules/ + +# IDE .idea/ .vscode/ *.swp *.swo *~ + +# OS .DS_Store +Thumbs.db -# Зависимости -/vendor/ -/node_modules/ - -# Логи -*.log -logs/ - -# Загруженные файлы пользователей -/uploads/products/* -!/uploads/products/.gitkeep - -# Конфигурационные файлы с секретами (если есть) +# Environment .env .env.local .env.*.local -# Кэш +# Logs +*.log +/logs/ + +# Storage (uploads are gitignored, keep structure) +/storage/uploads/* +!/storage/uploads/.gitkeep + +# Cache /cache/ *.cache -# Временные файлы +# Compiled assets +/public/assets/css/*.css +!/public/assets/css/.gitkeep + +# Docker volumes +/docker/data/ + +# Tests +/coverage/ +.phpunit.result.cache + +# Temporary files /tmp/ *.tmp - -# Скомпилированные CSS -*.css.map - -# База данных SQLite (если используется локально) -*.db -*.sqlite -*.sqlite3 - -# Файлы резервных копий -*.bak -*.backup - -# PHP debug/profiling -.phpunit.result.cache -phpunit.xml - diff --git a/.htaccess b/.htaccess deleted file mode 100644 index 2c0090b..0000000 --- a/.htaccess +++ /dev/null @@ -1,29 +0,0 @@ -# AETERNA MVC - Корневой .htaccess -# Перенаправляет все запросы в public/ - - - RewriteEngine On - - # Если запрос к assets (статика) - перенаправляем в public/assets - RewriteCond %{REQUEST_URI} ^/cite_practica/assets/ - RewriteRule ^assets/(.*)$ public/assets/$1 [L] - - # Если запрос к старым img директориям - оставляем как есть - RewriteCond %{REQUEST_URI} ^/cite_practica/img/ - RewriteRule ^img/(.*)$ img/$1 [L] - - RewriteCond %{REQUEST_URI} ^/cite_practica/img2/ - RewriteRule ^img2/(.*)$ img2/$1 [L] - - # Если это существующий файл (для обратной совместимости) - пропускаем - RewriteCond %{REQUEST_FILENAME} -f - RewriteRule ^ - [L] - - # Все остальное - в public/index.php - RewriteCond %{REQUEST_FILENAME} !-d - RewriteRule ^(.*)$ public/index.php [QSA,L] - - -# Отключаем просмотр директорий -Options -Indexes - diff --git a/Dockerfile b/Dockerfile index 75e71ec..1433ae0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,5 @@ FROM php:8.2-apache -# Установка расширений PHP RUN apt-get update && apt-get install -y \ libpq-dev \ libzip-dev \ @@ -9,26 +8,22 @@ RUN apt-get update && apt-get install -y \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* -# Включаем mod_rewrite -RUN a2enmod rewrite +RUN a2enmod rewrite headers expires -# Копируем конфигурацию Apache -COPY docker/apache/vhosts.conf /etc/apache2/sites-available/vhosts.conf +COPY docker/apache/vhosts.conf /etc/apache2/sites-available/000-default.conf COPY docker/apache/entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh -# Рабочая директория WORKDIR /var/www/html -# Копируем приложение COPY . /var/www/html/ -# Устанавливаем права -RUN chown -R www-data:www-data /var/www/html +RUN mkdir -p /var/www/html/storage/uploads \ + && mkdir -p /var/www/html/public/assets/css + +RUN chown -R www-data:www-data /var/www/html \ + && chmod -R 755 /var/www/html/storage -# Экспортируем порт EXPOSE 80 -# Точка входа ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] - diff --git a/README.md b/README.md new file mode 100644 index 0000000..548eab1 --- /dev/null +++ b/README.md @@ -0,0 +1,171 @@ +# AETERNA - Интернет-магазин мебели + +Современный интернет-магазин мебели на PHP с MVC архитектурой. + +## Структура проекта + +``` +aeterna/ +├── app/ # Приложение +│ ├── Controllers/ # Контроллеры +│ ├── Core/ # Ядро (App, Router, View, etc.) +│ ├── Models/ # Модели +│ └── Views/ # Шаблоны +├── config/ # Конфигурация +│ ├── app.php # Настройки приложения +│ ├── database.php # Настройки БД +│ └── routes.php # Маршруты +├── public/ # Публичная директория (DocumentRoot) +│ ├── index.php # Точка входа +│ ├── assets/ # Статические файлы +│ │ ├── css/ +│ │ ├── js/ +│ │ └── images/ # Изображения +│ └── .htaccess # Правила Apache +├── storage/ # Хранилище +│ └── uploads/ # Загруженные файлы +├── tests/ # Тесты +├── docker/ # Docker конфигурация +│ └── apache/ +├── docker-compose.yml +├── Dockerfile +└── README.md +``` + +## Требования + +- PHP 8.2+ +- PostgreSQL 14+ +- Apache с mod_rewrite или Nginx +- Docker (опционально) + +## Установка + +### Вариант 1: Docker (рекомендуется) + +```bash +# Клонировать репозиторий +git clone +cd aeterna + +# Запустить контейнеры +docker-compose up -d + +# Приложение будет доступно по адресу: +# http://localhost:8080 +``` + +### Вариант 2: Локальный сервер + +#### Apache + +1. Настройте DocumentRoot на директорию `public/` + +```apache + + ServerName aeterna.local + DocumentRoot /path/to/aeterna/public + + + Options -Indexes +FollowSymLinks + AllowOverride All + Require all granted + + +``` + +2. Включите mod_rewrite: +```bash +sudo a2enmod rewrite +sudo systemctl restart apache2 +``` + +#### Nginx + +```nginx +server { + listen 80; + server_name aeterna.local; + root /path/to/aeterna/public; + index index.php; + + location / { + try_files $uri $uri/ /index.php?$query_string; + } + + location ~ \.php$ { + fastcgi_pass unix:/var/run/php/php8.2-fpm.sock; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } +} +``` + +## Конфигурация + +### База данных + +Отредактируйте `config/database.php` или используйте переменные окружения: + +```bash +export DB_HOST=localhost +export DB_PORT=5432 +export DB_DATABASE=aeterna +export DB_USERNAME=user +export DB_PASSWORD=password +``` + +### Приложение + +Отредактируйте `config/app.php`: + +- `debug` - режим отладки (false для продакшена) +- `url` - URL приложения +- `admin_emails` - email адреса администраторов + +## Функционал + +### Для покупателей +- Каталог товаров с фильтрацией +- Корзина покупок +- Оформление заказов +- Регистрация и авторизация + +### Для администраторов +- Управление товарами +- Управление категориями +- Управление заказами +- Управление пользователями + +## Маршруты + +| Метод | URL | Описание | +|-------|-----|----------| +| GET | `/` | Главная страница | +| GET | `/catalog` | Каталог товаров | +| GET | `/product/{id}` | Страница товара | +| GET | `/cart` | Корзина | +| GET | `/login` | Вход | +| GET | `/register` | Регистрация | +| GET | `/admin` | Админ-панель | + +## Технологии + +- **Backend**: PHP 8.2, MVC архитектура +- **Database**: PostgreSQL +- **Frontend**: HTML5, CSS3/LESS, JavaScript, jQuery +- **Сервер**: Apache/Nginx +- **Контейнеризация**: Docker + +## Лицензия + +MIT License + +## Автор + +AETERNA Team + diff --git a/add_to_cart.php b/add_to_cart.php deleted file mode 100644 index 4ff29d8..0000000 --- a/add_to_cart.php +++ /dev/null @@ -1,116 +0,0 @@ - false, 'message' => 'Требуется авторизация']); - exit(); -} - -if ($_SERVER['REQUEST_METHOD'] == 'POST' && isset($_POST['product_id'])) { - $product_id = intval($_POST['product_id']); - $quantity = intval($_POST['quantity'] ?? 1); - $user_id = $_SESSION['user_id'] ?? 0; - - if ($user_id == 0) { - echo json_encode(['success' => false, 'message' => 'Пользователь не найден']); - exit(); - } - - $db = Database::getInstance()->getConnection(); - - try { - // Проверяем наличие товара на складе - $checkStock = $db->prepare(" - SELECT stock_quantity, name, price - FROM products - WHERE product_id = ? AND is_available = TRUE - "); - $checkStock->execute([$product_id]); - $product = $checkStock->fetch(); - - if (!$product) { - echo json_encode(['success' => false, 'message' => 'Товар не найден']); - exit(); - } - - if ($product['stock_quantity'] < $quantity) { - echo json_encode(['success' => false, 'message' => 'Недостаточно товара на складе']); - exit(); - } - - // Проверяем, есть ли товар уже в корзине пользователя - $checkCart = $db->prepare(" - SELECT cart_id, quantity - FROM cart - WHERE user_id = ? AND product_id = ? - "); - $checkCart->execute([$user_id, $product_id]); - $cartItem = $checkCart->fetch(); - - if ($cartItem) { - // Обновляем количество - $newQuantity = $cartItem['quantity'] + $quantity; - - // Проверяем общее количество - if ($newQuantity > $product['stock_quantity']) { - echo json_encode(['success' => false, 'message' => 'Превышено доступное количество']); - exit(); - } - - $updateStmt = $db->prepare(" - UPDATE cart - SET quantity = ?, updated_at = CURRENT_TIMESTAMP - WHERE cart_id = ? - "); - $updateStmt->execute([$newQuantity, $cartItem['cart_id']]); - } else { - // Добавляем новый товар - $insertStmt = $db->prepare(" - INSERT INTO cart (user_id, product_id, quantity) - VALUES (?, ?, ?) - "); - $insertStmt->execute([$user_id, $product_id, $quantity]); - } - - // Обновляем сессию - if (!isset($_SESSION['cart'])) { - $_SESSION['cart'] = []; - } - - if (isset($_SESSION['cart'][$product_id])) { - $_SESSION['cart'][$product_id]['quantity'] += $quantity; - } else { - $_SESSION['cart'][$product_id] = [ - 'quantity' => $quantity, - 'name' => $product['name'], - 'price' => $product['price'], - 'added_at' => time() - ]; - } - - // Получаем общее количество товаров в корзине - $cartCountStmt = $db->prepare(" - SELECT SUM(quantity) as total - FROM cart - WHERE user_id = ? - "); - $cartCountStmt->execute([$user_id]); - $cart_count = $cartCountStmt->fetchColumn() ?: 0; - - echo json_encode([ - 'success' => true, - 'cart_count' => $cart_count, - 'message' => 'Товар добавлен в корзину' - ]); - - } catch (PDOException $e) { - echo json_encode([ - 'success' => false, - 'message' => 'Ошибка базы данных: ' . $e->getMessage() - ]); - } -} else { - echo json_encode(['success' => false, 'message' => 'Неверный запрос']); -} -?> \ No newline at end of file diff --git a/admin_actions.php b/admin_actions.php deleted file mode 100644 index ea9563b..0000000 --- a/admin_actions.php +++ /dev/null @@ -1,170 +0,0 @@ -getConnection(); -$action = $_GET['action'] ?? ''; - -try { - switch ($action) { - case 'delete_product': - if (isset($_GET['id'])) { - $productId = intval($_GET['id']); - // Делаем товар недоступным - $stmt = $db->prepare(" - UPDATE products - SET is_available = FALSE, stock_quantity = 0, updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - $stmt->execute([$productId]); - header('Location: admin_panel.php?action=products&message=Товар помечен как недоступный'); - exit(); - } - break; - - case 'restore_product': - if (isset($_GET['id'])) { - $productId = intval($_GET['id']); - // Восстанавливаем товар - $stmt = $db->prepare(" - UPDATE products - SET is_available = TRUE, stock_quantity = 10, updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - $stmt->execute([$productId]); - header('Location: admin_panel.php?action=products&message=Товар восстановлен'); - exit(); - } - break; - - case 'delete_category': - if (isset($_GET['id'])) { - $categoryId = intval($_GET['id']); - - try { - // 1. Проверяем, есть ли товары в этой категории - $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); - $checkProducts->execute([$categoryId]); - $productCount = $checkProducts->fetchColumn(); - - if ($productCount > 0) { - // Если есть товары, делаем категорию неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - header('Location: admin_panel.php?action=categories&message=Категория скрыта (содержит товары)'); - exit(); - } - - // 2. Проверяем, есть ли дочерние категории - $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?"); - $checkChildren->execute([$categoryId]); - $childCount = $checkChildren->fetchColumn(); - - if ($childCount > 0) { - // Вариант A: Делаем категорию неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - header('Location: admin_panel.php?action=categories&message=Категория скрыта (имеет дочерние категории)'); - exit(); - - // Вариант B: Удаляем вместе с дочерними (раскомментируйте если нужно) - /* - // Сначала удаляем дочерние категории - $stmt = $db->prepare("DELETE FROM categories WHERE parent_id = ?"); - $stmt->execute([$categoryId]); - - // Затем удаляем саму категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория и её дочерние категории удалены'); - exit(); - */ - } - - // 3. Если нет товаров и дочерних категорий, удаляем - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория удалена'); - exit(); - - } catch (PDOException $e) { - header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage())); - exit(); - } - } - break; - - case 'delete_category_force': - // Принудительное удаление с дочерними категориями - if (isset($_GET['id'])) { - $categoryId = intval($_GET['id']); - - try { - // Сначала перемещаем товары в другую категорию (например, в первую) - $firstCategory = $db->query("SELECT category_id FROM categories WHERE category_id != ? LIMIT 1")->fetchColumn(); - - if ($firstCategory) { - $moveProducts = $db->prepare("UPDATE products SET category_id = ? WHERE category_id = ?"); - $moveProducts->execute([$firstCategory, $categoryId]); - } - - // Обнуляем parent_id у дочерних категорий - $stmt = $db->prepare("UPDATE categories SET parent_id = NULL WHERE parent_id = ?"); - $stmt->execute([$categoryId]); - - // Удаляем категорию - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория удалена. Товары перемещены.'); - exit(); - - } catch (PDOException $e) { - header('Location: admin_panel.php?action=categories&error=' . urlencode($e->getMessage())); - exit(); - } - } - break; - - case 'toggle_user': - if (isset($_GET['id'])) { - $userId = intval($_GET['id']); - $stmt = $db->prepare(" - UPDATE users - SET is_active = NOT is_active, updated_at = CURRENT_TIMESTAMP - WHERE user_id = ? - "); - $stmt->execute([$userId]); - header('Location: admin_panel.php?action=users&message=Статус пользователя изменен'); - exit(); - } - break; - - case 'make_admin': - if (isset($_GET['id'])) { - $userId = intval($_GET['id']); - $stmt = $db->prepare("UPDATE users SET is_admin = TRUE WHERE user_id = ?"); - $stmt->execute([$userId]); - header('Location: admin_panel.php?action=users&message=Пользователь назначен администратором'); - exit(); - } - break; - } -} catch (PDOException $e) { - header('Location: admin_panel.php?error=' . urlencode($e->getMessage())); - exit(); -} - -// Если действие не распознано -header('Location: admin_panel.php'); -exit(); -?> \ No newline at end of file diff --git a/admin_panel.php b/admin_panel.php deleted file mode 100644 index f8a4252..0000000 --- a/admin_panel.php +++ /dev/null @@ -1,772 +0,0 @@ -Сначала добавьте категории!'; -} - -// Проверка прав администратора -if (!isset($_SESSION['isAdmin']) || $_SESSION['isAdmin'] !== true) { - echo ""; - exit(); -} - -$db = Database::getInstance()->getConnection(); - -// Обработка действий -$action = $_GET['action'] ?? 'dashboard'; -$message = $_GET['message'] ?? ''; -$error = $_GET['error'] ?? ''; - -// Обработка POST запросов - ДОБАВЛЕНО ПРОСТОЕ И РАБОТАЮЩЕЕ! -if ($_SERVER['REQUEST_METHOD'] === 'POST') { - $post_action = $_POST['action'] ?? ''; - - try { - if ($post_action === 'add_category') { - $name = trim($_POST['name'] ?? ''); - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; - $description = trim($_POST['description'] ?? ''); - $sort_order = (int)($_POST['sort_order'] ?? 0); - $is_active = isset($_POST['is_active']) ? 1 : 0; - - if (empty($name)) { - throw new Exception('Название категории обязательно'); - } - - $stmt = $db->prepare(" - INSERT INTO categories (name, slug, parent_id, description, sort_order, is_active) - VALUES (?, ?, ?, ?, ?, ?) - "); - - $result = $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active]); - - if ($result) { - header('Location: admin_panel.php?action=categories&message=Категория+успешно+добавлена'); - exit(); - } - } - - // ИСПРАВЬТЕ БЛОК edit_category или добавьте его если его нет: - if ($post_action === 'edit_category' && isset($_POST['category_id'])) { - $category_id = (int)$_POST['category_id']; - $name = trim($_POST['name'] ?? ''); - $parent_id = !empty($_POST['parent_id']) ? (int)$_POST['parent_id'] : NULL; - $description = trim($_POST['description'] ?? ''); - $sort_order = (int)($_POST['sort_order'] ?? 0); - $is_active = isset($_POST['is_active']) ? 1 : 0; - - if (empty($name)) { - throw new Exception('Название категории обязательно'); - } - - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - - $stmt = $db->prepare(" - UPDATE categories SET - name = ?, - slug = ?, - parent_id = ?, - description = ?, - sort_order = ?, - is_active = ?, - updated_at = CURRENT_TIMESTAMP - WHERE category_id = ? - "); - - $stmt->execute([$name, $slug, $parent_id, $description, $sort_order, $is_active, $category_id]); - - header('Location: admin_panel.php?action=categories&message=Категория+обновлена'); - exit(); - } - - if ($post_action === 'add_product') { - $name = trim($_POST['name'] ?? ''); - $slug = strtolower(preg_replace('/[^a-z0-9]+/i', '-', $name)); - $category_id = (int)($_POST['category_id'] ?? 0); - $description = trim($_POST['description'] ?? ''); - $price = (float)($_POST['price'] ?? 0); - $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; - $sku = trim($_POST['sku'] ?? ''); - $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); - $is_available = isset($_POST['is_available']) ? 1 : 0; - $is_featured = isset($_POST['is_featured']) ? 1 : 0; - $image_url = trim($_POST['image_url'] ?? ''); - $color = trim($_POST['color'] ?? ''); - $material = trim($_POST['material'] ?? ''); - $card_size = trim($_POST['card_size'] ?? 'small'); - - - // ВАЖНО: Проверяем category_id - if ($category_id <= 0) { - $_SESSION['error'] = 'Выберите корректную категорию'; - header('Location: admin_panel.php?action=add_product'); - exit(); - } - - // Проверяем существование категории - $check_category = $db->prepare("SELECT COUNT(*) FROM categories WHERE category_id = ?"); - $check_category->execute([$category_id]); - if ($check_category->fetchColumn() == 0) { - $_SESSION['error'] = 'Выбранная категория не существует'; - header('Location: admin_panel.php?action=add_product'); - exit(); - } - - if (empty($name)) throw new Exception('Название товара обязательно'); - if ($price <= 0) throw new Exception('Цена должна быть больше 0'); - - // Генерируем SKU если пустой - if (empty($sku)) { - $sku = 'PROD-' . strtoupper(substr(preg_replace('/[^a-z0-9]/i', '', $name), 0, 6)) . '-' . rand(100, 999); - } - - $stmt = $db->prepare(" - INSERT INTO products ( - category_id, name, slug, description, price, old_price, - sku, stock_quantity, is_available, is_featured, image_url, - color, material, card_size - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - "); - - $result = $stmt->execute([ - $category_id, $name, $slug, $description, $price, $old_price, - $sku, $stock_quantity, $is_available, $is_featured, $image_url, - $color, $material, $card_size - ]); - - if ($result) { - $_SESSION['message'] = 'Товар успешно добавлен'; - header('Location: admin_panel.php?action=products'); - exit(); - } - } - - // ИСПРАВЛЕННЫЙ КОД для edit_product в admin_panel.php: - if ($post_action === 'edit_product' && isset($_POST['product_id'])) { - $product_id = (int)$_POST['product_id']; - $name = trim($_POST['name'] ?? ''); - $category_id = (int)($_POST['category_id'] ?? 1); // ПО УМОЛЧАНИЮ 1, чтобы избежать 0 - $description = trim($_POST['description'] ?? ''); - $price = (float)($_POST['price'] ?? 0); - $old_price = !empty($_POST['old_price']) ? (float)$_POST['old_price'] : NULL; - $stock_quantity = (int)($_POST['stock_quantity'] ?? 0); - $is_available = isset($_POST['is_available']) ? 1 : 0; - $image_url = trim($_POST['image_url'] ?? ''); - $color = trim($_POST['color'] ?? ''); - $material = trim($_POST['material'] ?? ''); - - // ВАЖНО: Проверяем category_id - if ($category_id <= 0) { - // Если category_id = 0, устанавливаем первую доступную категорию - $firstCat = $db->query("SELECT category_id FROM categories LIMIT 1")->fetchColumn(); - $category_id = $firstCat ?: 1; - } - - $stmt = $db->prepare(" - UPDATE products SET - name = ?, - category_id = ?, - description = ?, - price = ?, - old_price = ?, - stock_quantity = ?, - is_available = ?, - image_url = ?, - color = ?, - material = ?, - updated_at = CURRENT_TIMESTAMP - WHERE product_id = ? - "); - - $stmt->execute([ - $name, $category_id, $description, $price, $old_price, - $stock_quantity, $is_available, $image_url, $color, $material, $product_id - ]); - - header('Location: admin_panel.php?action=products&message=Товар+обновлен'); - exit(); - } - - if ($post_action === 'delete_category' && isset($_POST['category_id'])) { - $categoryId = intval($_POST['category_id']); - - // 1. Проверяем, есть ли товары в этой категории - $checkProducts = $db->prepare("SELECT COUNT(*) FROM products WHERE category_id = ?"); - $checkProducts->execute([$categoryId]); - $productCount = $checkProducts->fetchColumn(); - - // 2. Проверяем, есть ли дочерние категории - $checkChildren = $db->prepare("SELECT COUNT(*) FROM categories WHERE parent_id = ?"); - $checkChildren->execute([$categoryId]); - $childCount = $checkChildren->fetchColumn(); - - if ($productCount > 0) { - // Если есть товары, делаем категорию неактивной вместо удаления - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(содержит+товары)'); - exit(); - } elseif ($childCount > 0) { - // Если есть дочерние категории, делаем неактивной - $stmt = $db->prepare("UPDATE categories SET is_active = FALSE WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+скрыта+(имеет+дочерние+категории)'); - exit(); - } else { - // Если нет товаров и дочерних категорий, удаляем - $stmt = $db->prepare("DELETE FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - - header('Location: admin_panel.php?action=categories&message=Категория+удалена'); - exit(); - } - } - } catch (PDOException $e) { - header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode('Ошибка БД: ' . $e->getMessage())); - exit(); - } catch (Exception $e) { - header('Location: admin_panel.php?action=' . $action . '&error=' . urlencode($e->getMessage())); - exit(); - } - } - - -// Получение данных для отображения -try { - // Статистика - $stats = [ - 'total_products' => $db->query("SELECT COUNT(*) FROM products")->fetchColumn(), - 'active_products' => $db->query("SELECT COUNT(*) FROM products WHERE is_available = TRUE")->fetchColumn(), - 'total_orders' => $db->query("SELECT COUNT(*) FROM orders")->fetchColumn(), - 'total_users' => $db->query("SELECT COUNT(*) FROM users")->fetchColumn(), - 'revenue' => $db->query("SELECT COALESCE(SUM(final_amount), 0) FROM orders WHERE status = 'completed'")->fetchColumn() - ]; - - // Получаем все категории - $allCategories = $db->query("SELECT * FROM categories WHERE is_active = TRUE ORDER BY name")->fetchAll(); - - // Получаем родительские категории - $parentCategories = $db->query("SELECT * FROM categories WHERE parent_id IS NULL AND is_active = TRUE ORDER BY name")->fetchAll(); - - switch ($action) { - case 'products': - $showAll = isset($_GET['show_all']) && $_GET['show_all'] == '1'; - $sql = $showAll - ? "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id ORDER BY p.created_at DESC" - : "SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.category_id WHERE p.is_available = TRUE ORDER BY p.created_at DESC"; - $data = $db->query($sql)->fetchAll(); - break; - - case 'categories': - $data = $db->query(" - SELECT c1.*, c2.name as parent_name, - (SELECT COUNT(*) FROM products p WHERE p.category_id = c1.category_id) as product_count - FROM categories c1 - LEFT JOIN categories c2 ON c1.parent_id = c2.category_id - ORDER BY c1.sort_order, c1.name - ")->fetchAll(); - break; - - case 'orders': - $data = $db->query(" - SELECT o.*, u.email as user_email - FROM orders o - LEFT JOIN users u ON o.user_id = u.user_id - ORDER BY o.created_at DESC - LIMIT 50 - ")->fetchAll(); - break; - - case 'users': - $data = $db->query("SELECT * FROM users ORDER BY created_at DESC LIMIT 50")->fetchAll(); - break; - - case 'add_product': - case 'edit_product': - if ($action === 'edit_product' && isset($_GET['id'])) { - $productId = (int)$_GET['id']; - $stmt = $db->prepare("SELECT * FROM products WHERE product_id = ?"); - $stmt->execute([$productId]); - $edit_data = $stmt->fetch(); - } - break; - - case 'add_category': - case 'edit_category': - if ($action === 'edit_category' && isset($_GET['id'])) { - $categoryId = (int)$_GET['id']; - $stmt = $db->prepare("SELECT * FROM categories WHERE category_id = ?"); - $stmt->execute([$categoryId]); - $edit_data = $stmt->fetch(); - } - break; - - } - -} catch (PDOException $e) { - $error = "Ошибка базы данных: " . $e->getMessage(); -} -?> - - - - - - AETERNA - Админ-панель - - - - -
-

Админ-панель AETERNA

-
- - В каталог - Выйти -
-
- - - -
- -
- -
- - - -
- -
- - - - -

Статистика

-
-
-

-

Всего товаров

-
-
-

-

Активных товаров

-
-
-

-

Заказов

-
-
-

-

Пользователей

-
-
- -
- - Добавить новый товар - - - Добавить категорию - -
- - - -
-

Управление товарами

-
- - Добавить товар - - - Только активные - - Показать все - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - -
IDНазваниеКатегорияЦенаНа складеСтатусДействия
- 0): ?> - ✓ Доступен - - ✗ Недоступен - - ⚠ Нет на складе - - - - - - -
- - - - -
- -
- - - - -
- -
- - - -
-

Управление категориями

- - Добавить категорию - -
- - - - - - - - - - - - - - - - - - - - - - - - -
IDНазваниеSlugРодительскаяТоваровДействия
- - - Редактировать - - - - -
- - - -
-

- -
- - - - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - Отмена -
-
- - - -
-

- -
- - - - - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - Отмена -
-
- - - -

Заказы

- - - - - - - - - - - - - - - - - - - - - - - -
№ заказаКлиентСуммаСтатусДатаДействия
- - - -
- - - -

Пользователи

- - - - - - - - - - - - - - - - - - - - - -
IDEmailФИОДата регистрацииСтатус
- - ✓ Активен - - ✗ Неактивен - -
- - -
- - - \ No newline at end of file diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php index 3053536..f876cd6 100644 --- a/app/Controllers/AdminController.php +++ b/app/Controllers/AdminController.php @@ -8,9 +8,6 @@ use App\Models\Category; use App\Models\Order; use App\Models\User; -/** - * AdminController - контроллер админ-панели - */ class AdminController extends Controller { private Product $productModel; @@ -26,9 +23,6 @@ class AdminController extends Controller $this->userModel = new User(); } - /** - * Дашборд - */ public function dashboard(): void { $this->requireAdmin(); @@ -47,11 +41,6 @@ class AdminController extends Controller ], 'admin'); } - // ========== Товары ========== - - /** - * Список товаров - */ public function products(): void { $this->requireAdmin(); @@ -68,9 +57,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Форма добавления товара - */ public function addProduct(): void { $this->requireAdmin(); @@ -85,9 +71,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Сохранение нового товара - */ public function storeProduct(): void { $this->requireAdmin(); @@ -116,9 +99,6 @@ class AdminController extends Controller } } - /** - * Форма редактирования товара - */ public function editProduct(int $id): void { $this->requireAdmin(); @@ -140,9 +120,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Обновление товара - */ public function updateProduct(int $id): void { $this->requireAdmin(); @@ -168,9 +145,6 @@ class AdminController extends Controller } } - /** - * Удаление товара (делаем недоступным) - */ public function deleteProduct(int $id): void { $this->requireAdmin(); @@ -179,11 +153,6 @@ class AdminController extends Controller $this->redirect('/admin/products?message=' . urlencode('Товар скрыт')); } - // ========== Категории ========== - - /** - * Список категорий - */ public function categories(): void { $this->requireAdmin(); @@ -198,9 +167,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Форма добавления категории - */ public function addCategory(): void { $this->requireAdmin(); @@ -215,9 +181,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Сохранение категории - */ public function storeCategory(): void { $this->requireAdmin(); @@ -238,9 +201,6 @@ class AdminController extends Controller } } - /** - * Форма редактирования категории - */ public function editCategory(int $id): void { $this->requireAdmin(); @@ -262,9 +222,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Обновление категории - */ public function updateCategory(int $id): void { $this->requireAdmin(); @@ -285,9 +242,6 @@ class AdminController extends Controller } } - /** - * Удаление категории - */ public function deleteCategory(int $id): void { $this->requireAdmin(); @@ -304,11 +258,6 @@ class AdminController extends Controller } } - // ========== Заказы ========== - - /** - * Список заказов - */ public function orders(): void { $this->requireAdmin(); @@ -321,9 +270,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Детали заказа - */ public function orderDetails(int $id): void { $this->requireAdmin(); @@ -341,9 +287,6 @@ class AdminController extends Controller ], 'admin'); } - /** - * Обновление статуса заказа - */ public function updateOrderStatus(int $id): void { $this->requireAdmin(); @@ -354,11 +297,6 @@ class AdminController extends Controller $this->redirect('/admin/orders/' . $id); } - // ========== Пользователи ========== - - /** - * Список пользователей - */ public function users(): void { $this->requireAdmin(); @@ -371,4 +309,3 @@ class AdminController extends Controller ], 'admin'); } } - diff --git a/app/Controllers/AuthController.php b/app/Controllers/AuthController.php index 210794e..52d8a15 100644 --- a/app/Controllers/AuthController.php +++ b/app/Controllers/AuthController.php @@ -5,9 +5,6 @@ namespace App\Controllers; use App\Core\Controller; use App\Models\User; -/** - * AuthController - контроллер авторизации - */ class AuthController extends Controller { private User $userModel; @@ -17,9 +14,6 @@ class AuthController extends Controller $this->userModel = new User(); } - /** - * Форма входа - */ public function loginForm(): void { if ($this->isAuthenticated()) { @@ -35,9 +29,6 @@ class AuthController extends Controller ]); } - /** - * Обработка входа - */ public function login(): void { $email = $this->getPost('email', ''); @@ -62,7 +53,6 @@ class AuthController extends Controller return; } - // Устанавливаем сессию $this->setSession($user); $this->json([ @@ -71,9 +61,6 @@ class AuthController extends Controller ]); } - /** - * Форма регистрации - */ public function registerForm(): void { if ($this->isAuthenticated()) { @@ -86,15 +73,11 @@ class AuthController extends Controller 'success' => $_SESSION['registration_success'] ?? null ]); - // Очищаем flash данные unset($_SESSION['registration_errors']); unset($_SESSION['old_data']); unset($_SESSION['registration_success']); } - /** - * Обработка регистрации - */ public function register(): void { $errors = []; @@ -107,7 +90,6 @@ class AuthController extends Controller $confirmPassword = $this->getPost('confirm-password', ''); $privacy = $this->getPost('privacy'); - // Валидация if (empty($fullName) || strlen($fullName) < 3) { $errors[] = 'ФИО должно содержать минимум 3 символа'; } @@ -136,7 +118,6 @@ class AuthController extends Controller $errors[] = 'Необходимо согласие с условиями обработки персональных данных'; } - // Проверяем существование email if (empty($errors) && $this->userModel->emailExists($email)) { $errors[] = 'Пользователь с таким email уже существует'; } @@ -153,7 +134,6 @@ class AuthController extends Controller return; } - // Создаем пользователя try { $userId = $this->userModel->register([ 'email' => $email, @@ -167,10 +147,7 @@ class AuthController extends Controller throw new \Exception('Ошибка при создании пользователя'); } - // Получаем созданного пользователя $user = $this->userModel->find($userId); - - // Устанавливаем сессию $this->setSession($user); $_SESSION['registration_success'] = 'Регистрация прошла успешно!'; @@ -188,9 +165,6 @@ class AuthController extends Controller } } - /** - * Выход из системы - */ public function logout(): void { session_destroy(); @@ -199,9 +173,6 @@ class AuthController extends Controller $this->redirect('/'); } - /** - * Установить сессию пользователя - */ private function setSession(array $user): void { $_SESSION['user_id'] = $user['user_id']; @@ -214,4 +185,3 @@ class AuthController extends Controller $_SESSION['login_time'] = time(); } } - diff --git a/app/Controllers/CartController.php b/app/Controllers/CartController.php index d3368cc..6176046 100644 --- a/app/Controllers/CartController.php +++ b/app/Controllers/CartController.php @@ -6,9 +6,6 @@ use App\Core\Controller; use App\Models\Cart; use App\Models\Product; -/** - * CartController - контроллер корзины - */ class CartController extends Controller { private Cart $cartModel; @@ -20,9 +17,6 @@ class CartController extends Controller $this->productModel = new Product(); } - /** - * Страница корзины - */ public function index(): void { $this->requireAuth(); @@ -39,9 +33,6 @@ class CartController extends Controller ]); } - /** - * Добавить товар в корзину - */ public function add(): void { if (!$this->isAuthenticated()) { @@ -64,7 +55,6 @@ class CartController extends Controller return; } - // Проверяем наличие товара $product = $this->productModel->find($productId); if (!$product || !$product['is_available']) { @@ -75,7 +65,6 @@ class CartController extends Controller return; } - // Проверяем количество на складе $cartItem = $this->cartModel->getItem($userId, $productId); $currentQty = $cartItem ? $cartItem['quantity'] : 0; $newQty = $currentQty + $quantity; @@ -88,7 +77,6 @@ class CartController extends Controller return; } - // Добавляем в корзину $result = $this->cartModel->addItem($userId, $productId, $quantity); if ($result) { @@ -106,9 +94,6 @@ class CartController extends Controller } } - /** - * Обновить количество товара - */ public function update(): void { if (!$this->isAuthenticated()) { @@ -124,7 +109,6 @@ class CartController extends Controller $userId = $this->getCurrentUser()['id']; if ($quantity <= 0) { - // Если количество 0 или меньше - удаляем $this->cartModel->removeItem($userId, $productId); $cartCount = $this->cartModel->getCount($userId); $this->json([ @@ -134,7 +118,6 @@ class CartController extends Controller return; } - // Проверяем наличие на складе $product = $this->productModel->find($productId); if (!$product || $quantity > $product['stock_quantity']) { $this->json([ @@ -161,9 +144,6 @@ class CartController extends Controller } } - /** - * Удалить товар из корзины - */ public function remove(): void { if (!$this->isAuthenticated()) { @@ -193,9 +173,6 @@ class CartController extends Controller } } - /** - * Получить количество товаров в корзине - */ public function count(): void { if (!$this->isAuthenticated()) { @@ -215,4 +192,3 @@ class CartController extends Controller ]); } } - diff --git a/app/Controllers/HomeController.php b/app/Controllers/HomeController.php index af41ca7..cf86f92 100644 --- a/app/Controllers/HomeController.php +++ b/app/Controllers/HomeController.php @@ -4,14 +4,8 @@ namespace App\Controllers; use App\Core\Controller; -/** - * HomeController - контроллер главной страницы - */ class HomeController extends Controller { - /** - * Главная страница - */ public function index(): void { $user = $this->getCurrentUser(); @@ -23,4 +17,3 @@ class HomeController extends Controller ]); } } - diff --git a/app/Controllers/OrderController.php b/app/Controllers/OrderController.php index 01c8a2a..bf38d9c 100644 --- a/app/Controllers/OrderController.php +++ b/app/Controllers/OrderController.php @@ -6,9 +6,6 @@ use App\Core\Controller; use App\Models\Order; use App\Models\Cart; -/** - * OrderController - контроллер заказов - */ class OrderController extends Controller { private Order $orderModel; @@ -20,9 +17,6 @@ class OrderController extends Controller $this->cartModel = new Cart(); } - /** - * Страница оформления заказа (корзина) - */ public function checkout(): void { $this->requireAuth(); @@ -39,9 +33,6 @@ class OrderController extends Controller ]); } - /** - * Создание заказа - */ public function create(): void { if (!$this->isAuthenticated()) { @@ -63,7 +54,6 @@ class OrderController extends Controller return; } - // Получаем данные заказа $orderData = [ 'customer_name' => $this->getPost('full_name', $user['full_name']), 'customer_email' => $this->getPost('email', $user['email']), @@ -79,7 +69,6 @@ class OrderController extends Controller 'notes' => $this->getPost('notes', '') ]; - // Валидация if (empty($orderData['customer_name'])) { $this->json([ 'success' => false, @@ -122,4 +111,3 @@ class OrderController extends Controller } } } - diff --git a/app/Controllers/PageController.php b/app/Controllers/PageController.php index 255a26a..a9faf44 100644 --- a/app/Controllers/PageController.php +++ b/app/Controllers/PageController.php @@ -4,14 +4,8 @@ namespace App\Controllers; use App\Core\Controller; -/** - * PageController - контроллер статических страниц - */ class PageController extends Controller { - /** - * Страница услуг - */ public function services(): void { $this->view('pages/services', [ @@ -20,9 +14,6 @@ class PageController extends Controller ]); } - /** - * Страница доставки и оплаты - */ public function delivery(): void { $this->view('pages/delivery', [ @@ -31,9 +22,6 @@ class PageController extends Controller ]); } - /** - * Страница гарантии - */ public function warranty(): void { $this->view('pages/warranty', [ @@ -42,4 +30,3 @@ class PageController extends Controller ]); } } - diff --git a/app/Controllers/ProductController.php b/app/Controllers/ProductController.php index e2b34e5..68683ad 100644 --- a/app/Controllers/ProductController.php +++ b/app/Controllers/ProductController.php @@ -6,9 +6,6 @@ use App\Core\Controller; use App\Models\Product; use App\Models\Category; -/** - * ProductController - контроллер товаров и каталога - */ class ProductController extends Controller { private Product $productModel; @@ -20,9 +17,6 @@ class ProductController extends Controller $this->categoryModel = new Category(); } - /** - * Каталог товаров - */ public function catalog(): void { $this->requireAuth(); @@ -30,7 +24,6 @@ class ProductController extends Controller $user = $this->getCurrentUser(); $isAdmin = $this->isAdmin(); - // Получаем параметры фильтрации $filters = [ 'category_id' => (int) $this->getQuery('category', 0), 'search' => $this->getQuery('search', ''), @@ -42,7 +35,6 @@ class ProductController extends Controller $showAll = $isAdmin && $this->getQuery('show_all') === '1'; - // Получаем данные $categories = $this->categoryModel->getActive(); $products = $showAll ? $this->productModel->getAllForAdmin(true) @@ -51,7 +43,6 @@ class ProductController extends Controller $availableColors = $this->productModel->getAvailableColors(); $availableMaterials = $this->productModel->getAvailableMaterials(); - // Подкатегории для выбранной категории $subcategories = []; if ($filters['category_id'] > 0) { $subcategories = $this->categoryModel->getChildren($filters['category_id']); @@ -72,9 +63,6 @@ class ProductController extends Controller ]); } - /** - * Страница товара - */ public function show(int $id): void { $this->requireAuth(); @@ -99,4 +87,3 @@ class ProductController extends Controller ]); } } - diff --git a/app/Core/App.php b/app/Core/App.php index f39c329..187d5d0 100644 --- a/app/Core/App.php +++ b/app/Core/App.php @@ -2,66 +2,68 @@ namespace App\Core; -/** - * App - главный класс приложения - */ class App { private Router $router; private static ?App $instance = null; + private array $config = []; public function __construct() { self::$instance = $this; - - // Регистрируем автозагрузчик сразу $this->registerAutoloader(); - $this->router = new Router(); } - /** - * Получить экземпляр приложения - */ public static function getInstance(): ?self { return self::$instance; } - /** - * Получить роутер - */ public function getRouter(): Router { return $this->router; } - /** - * Инициализация приложения - */ + public function getConfig(string $key = null) + { + if ($key === null) { + return $this->config; + } + return $this->config[$key] ?? null; + } + public function init(): self { - // Запускаем сессию + $this->loadConfig(); + date_default_timezone_set($this->config['timezone'] ?? 'Europe/Moscow'); + if (session_status() === PHP_SESSION_NONE) { session_start(); } - // Настраиваем обработку ошибок $this->setupErrorHandling(); - - // Загружаем маршруты $this->loadRoutes(); return $this; } - /** - * Регистрация автозагрузчика классов - */ + private function loadConfig(): void + { + $configPath = $this->getBasePath() . '/config/app.php'; + if (file_exists($configPath)) { + $this->config = require $configPath; + } + } + + public function getBasePath(): string + { + return defined('ROOT_PATH') ? ROOT_PATH : dirname(__DIR__, 2); + } + private function registerAutoloader(): void { spl_autoload_register(function ($class) { - // Преобразуем namespace в путь к файлу $prefix = 'App\\'; $baseDir = dirname(__DIR__) . '/'; @@ -79,14 +81,9 @@ class App }); } - /** - * Настройка обработки ошибок - */ private function setupErrorHandling(): void { - $config = require dirname(__DIR__, 2) . '/config/app.php'; - - if ($config['debug'] ?? false) { + if ($this->config['debug'] ?? false) { error_reporting(E_ALL); ini_set('display_errors', '1'); } else { @@ -103,16 +100,11 @@ class App }); } - /** - * Обработка исключений - */ private function handleException(\Throwable $e): void { - $config = require dirname(__DIR__, 2) . '/config/app.php'; - http_response_code(500); - if ($config['debug'] ?? false) { + if ($this->config['debug'] ?? false) { echo "

Ошибка приложения

"; echo "

Сообщение: " . htmlspecialchars($e->getMessage()) . "

"; echo "

Файл: " . htmlspecialchars($e->getFile()) . ":" . $e->getLine() . "

"; @@ -122,12 +114,9 @@ class App } } - /** - * Загрузка маршрутов - */ private function loadRoutes(): void { - $routesFile = dirname(__DIR__, 2) . '/config/routes.php'; + $routesFile = $this->getBasePath() . '/config/routes.php'; if (file_exists($routesFile)) { $router = $this->router; @@ -135,21 +124,17 @@ class App } } - /** - * Запуск приложения - */ public function run(): void { $uri = $_SERVER['REQUEST_URI'] ?? '/'; $method = $_SERVER['REQUEST_METHOD'] ?? 'GET'; - // Удаляем базовый путь, если он есть - $basePath = '/cite_practica'; - if (strpos($uri, $basePath) === 0) { + $basePath = $this->config['base_path'] ?? ''; + + if (!empty($basePath) && strpos($uri, $basePath) === 0) { $uri = substr($uri, strlen($basePath)); } - // Если URI пустой, делаем его корневым if (empty($uri) || $uri === false) { $uri = '/'; } @@ -161,4 +146,3 @@ class App } } } - diff --git a/app/Core/Controller.php b/app/Core/Controller.php index 125f52a..a479b6c 100644 --- a/app/Core/Controller.php +++ b/app/Core/Controller.php @@ -2,35 +2,20 @@ namespace App\Core; -/** - * Controller - базовый класс контроллера - */ abstract class Controller { - /** - * Данные для передачи в представление - */ protected array $data = []; - /** - * Отрендерить представление - */ protected function view(string $view, array $data = [], string $layout = 'main'): void { echo View::render($view, $data, $layout); } - /** - * Отрендерить представление без layout - */ protected function viewPartial(string $view, array $data = []): void { echo View::render($view, $data, null); } - /** - * Вернуть JSON ответ - */ protected function json(array $data, int $statusCode = 200): void { http_response_code($statusCode); @@ -39,18 +24,12 @@ abstract class Controller exit; } - /** - * Редирект на другой URL - */ protected function redirect(string $url): void { header("Location: {$url}"); exit; } - /** - * Получить текущего пользователя из сессии - */ protected function getCurrentUser(): ?array { if (!isset($_SESSION['isLoggedIn']) || $_SESSION['isLoggedIn'] !== true) { @@ -68,25 +47,16 @@ abstract class Controller ]; } - /** - * Проверить, авторизован ли пользователь - */ protected function isAuthenticated(): bool { return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true; } - /** - * Проверить, является ли пользователь администратором - */ protected function isAdmin(): bool { return $this->isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; } - /** - * Требовать авторизацию - */ protected function requireAuth(): void { if (!$this->isAuthenticated()) { @@ -95,9 +65,6 @@ abstract class Controller } } - /** - * Требовать права администратора - */ protected function requireAdmin(): void { if (!$this->isAdmin()) { @@ -105,9 +72,6 @@ abstract class Controller } } - /** - * Получить POST данные - */ protected function getPost(?string $key = null, $default = null) { if ($key === null) { @@ -116,9 +80,6 @@ abstract class Controller return $_POST[$key] ?? $default; } - /** - * Получить GET данные - */ protected function getQuery(?string $key = null, $default = null) { if ($key === null) { @@ -127,17 +88,11 @@ abstract class Controller return $_GET[$key] ?? $default; } - /** - * Установить flash-сообщение - */ protected function setFlash(string $type, string $message): void { $_SESSION['flash'][$type] = $message; } - /** - * Получить flash-сообщение - */ protected function getFlash(string $type): ?string { $message = $_SESSION['flash'][$type] ?? null; @@ -145,4 +100,3 @@ abstract class Controller return $message; } } - diff --git a/app/Core/Database.php b/app/Core/Database.php index 4aa2af6..2d553f3 100644 --- a/app/Core/Database.php +++ b/app/Core/Database.php @@ -2,9 +2,6 @@ namespace App\Core; -/** - * Database - Singleton класс для подключения к PostgreSQL - */ class Database { private static ?Database $instance = null; @@ -38,9 +35,6 @@ class Database return $this->connection; } - /** - * Выполнить SELECT запрос - */ public function query(string $sql, array $params = []): array { $stmt = $this->connection->prepare($sql); @@ -48,9 +42,6 @@ class Database return $stmt->fetchAll(); } - /** - * Выполнить SELECT запрос и получить одну запись - */ public function queryOne(string $sql, array $params = []): ?array { $stmt = $this->connection->prepare($sql); @@ -59,52 +50,36 @@ class Database return $result ?: null; } - /** - * Выполнить INSERT/UPDATE/DELETE запрос - */ public function execute(string $sql, array $params = []): bool { $stmt = $this->connection->prepare($sql); return $stmt->execute($params); } - /** - * Получить ID последней вставленной записи - */ public function lastInsertId(): string { return $this->connection->lastInsertId(); } - /** - * Начать транзакцию - */ public function beginTransaction(): bool { return $this->connection->beginTransaction(); } - /** - * Подтвердить транзакцию - */ public function commit(): bool { return $this->connection->commit(); } - /** - * Откатить транзакцию - */ public function rollBack(): bool { return $this->connection->rollBack(); } - // Запрещаем клонирование и десериализацию private function __clone() {} + public function __wakeup() { throw new \Exception("Десериализация Singleton запрещена"); } } - diff --git a/app/Core/Model.php b/app/Core/Model.php index 8063e7a..672389a 100644 --- a/app/Core/Model.php +++ b/app/Core/Model.php @@ -2,9 +2,6 @@ namespace App\Core; -/** - * Model - базовый класс модели - */ abstract class Model { protected Database $db; @@ -16,18 +13,12 @@ abstract class Model $this->db = Database::getInstance(); } - /** - * Найти запись по первичному ключу - */ public function find(int $id): ?array { $sql = "SELECT * FROM {$this->table} WHERE {$this->primaryKey} = ?"; return $this->db->queryOne($sql, [$id]); } - /** - * Получить все записи - */ public function all(?string $orderBy = null): array { $sql = "SELECT * FROM {$this->table}"; @@ -37,9 +28,6 @@ abstract class Model return $this->db->query($sql); } - /** - * Найти записи по условию - */ public function where(array $conditions, ?string $orderBy = null): array { $where = []; @@ -59,9 +47,6 @@ abstract class Model return $this->db->query($sql, $params); } - /** - * Найти одну запись по условию - */ public function findWhere(array $conditions): ?array { $where = []; @@ -76,9 +61,6 @@ abstract class Model return $this->db->queryOne($sql, $params); } - /** - * Создать новую запись - */ public function create(array $data): ?int { $columns = array_keys($data); @@ -98,9 +80,6 @@ abstract class Model return (int) $stmt->fetchColumn(); } - /** - * Обновить запись - */ public function update(int $id, array $data): bool { $set = []; @@ -122,18 +101,12 @@ abstract class Model return $this->db->execute($sql, $params); } - /** - * Удалить запись - */ public function delete(int $id): bool { $sql = "DELETE FROM {$this->table} WHERE {$this->primaryKey} = ?"; return $this->db->execute($sql, [$id]); } - /** - * Подсчитать количество записей - */ public function count(array $conditions = []): int { $sql = "SELECT COUNT(*) FROM {$this->table}"; @@ -153,28 +126,18 @@ abstract class Model return (int) $stmt->fetchColumn(); } - /** - * Выполнить произвольный SQL запрос - */ protected function query(string $sql, array $params = []): array { return $this->db->query($sql, $params); } - /** - * Выполнить произвольный SQL запрос и получить одну запись - */ protected function queryOne(string $sql, array $params = []): ?array { return $this->db->queryOne($sql, $params); } - /** - * Выполнить произвольный SQL запрос (INSERT/UPDATE/DELETE) - */ protected function execute(string $sql, array $params = []): bool { return $this->db->execute($sql, $params); } } - diff --git a/app/Core/Router.php b/app/Core/Router.php index 44b1a03..7180f88 100644 --- a/app/Core/Router.php +++ b/app/Core/Router.php @@ -2,17 +2,11 @@ namespace App\Core; -/** - * Router - маршрутизатор запросов - */ class Router { private array $routes = []; private array $params = []; - /** - * Добавить маршрут - */ public function add(string $method, string $route, string $controller, string $action): self { $this->routes[] = [ @@ -24,25 +18,16 @@ class Router return $this; } - /** - * GET маршрут - */ public function get(string $route, string $controller, string $action): self { return $this->add('GET', $route, $controller, $action); } - /** - * POST маршрут - */ public function post(string $route, string $controller, string $action): self { return $this->add('POST', $route, $controller, $action); } - /** - * Найти маршрут по URL и методу - */ public function match(string $url, string $method): ?array { $method = strtoupper($method); @@ -57,7 +42,6 @@ class Router $pattern = $this->convertRouteToRegex($route['route']); if (preg_match($pattern, $url, $matches)) { - // Извлекаем параметры из URL $this->params = $this->extractParams($route['route'], $matches); return [ @@ -71,27 +55,16 @@ class Router return null; } - /** - * Преобразовать маршрут в регулярное выражение - */ private function convertRouteToRegex(string $route): string { $route = trim($route, '/'); - - // Заменяем {param} на regex группу $pattern = preg_replace('/\{([a-zA-Z_]+)\}/', '([^/]+)', $route); - return '#^' . $pattern . '$#'; } - /** - * Извлечь параметры из совпадений - */ private function extractParams(string $route, array $matches): array { $params = []; - - // Находим все {param} в маршруте preg_match_all('/\{([a-zA-Z_]+)\}/', $route, $paramNames); foreach ($paramNames[1] as $index => $name) { @@ -103,9 +76,6 @@ class Router return $params; } - /** - * Удалить query string из URL - */ private function removeQueryString(string $url): string { if ($pos = strpos($url, '?')) { @@ -114,17 +84,11 @@ class Router return $url; } - /** - * Получить параметры маршрута - */ public function getParams(): array { return $this->params; } - /** - * Диспетчеризация запроса - */ public function dispatch(string $url, string $method): void { $match = $this->match($url, $method); @@ -148,8 +112,6 @@ class Router throw new \Exception("Метод {$action} не найден в контроллере {$controllerClass}"); } - // Вызываем метод контроллера с параметрами call_user_func_array([$controller, $action], $match['params']); } } - diff --git a/app/Core/View.php b/app/Core/View.php index 4e975f3..ef3c09d 100644 --- a/app/Core/View.php +++ b/app/Core/View.php @@ -2,24 +2,15 @@ namespace App\Core; -/** - * View - класс для рендеринга представлений - */ class View { private static string $viewsPath = ''; - /** - * Установить путь к директории представлений - */ public static function setViewsPath(string $path): void { self::$viewsPath = rtrim($path, '/'); } - /** - * Получить путь к директории представлений - */ public static function getViewsPath(): string { if (empty(self::$viewsPath)) { @@ -28,9 +19,6 @@ class View return self::$viewsPath; } - /** - * Отрендерить представление - */ public static function render(string $view, array $data = [], ?string $layout = 'main'): string { $viewPath = self::getViewsPath() . '/' . str_replace('.', '/', $view) . '.php'; @@ -39,15 +27,12 @@ class View throw new \Exception("Представление не найдено: {$viewPath}"); } - // Извлекаем данные в переменные extract($data); - // Буферизируем вывод контента ob_start(); require $viewPath; $content = ob_get_clean(); - // Если есть layout, оборачиваем контент if ($layout !== null) { $layoutPath = self::getViewsPath() . '/layouts/' . $layout . '.php'; @@ -63,9 +48,6 @@ class View return $content; } - /** - * Отрендерить partial (часть шаблона) - */ public static function partial(string $partial, array $data = []): string { $partialPath = self::getViewsPath() . '/partials/' . $partial . '.php'; @@ -81,49 +63,31 @@ class View return ob_get_clean(); } - /** - * Экранирование HTML - */ public static function escape(string $value): string { return htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); } - /** - * Сокращенный алиас для escape - */ public static function e(string $value): string { return self::escape($value); } - /** - * Форматирование цены - */ public static function formatPrice($price): string { return number_format((float)$price, 0, '', ' ') . ' ₽'; } - /** - * Форматирование даты - */ public static function formatDate(string $date, string $format = 'd.m.Y'): string { return date($format, strtotime($date)); } - /** - * Форматирование даты и времени - */ public static function formatDateTime(string $date, string $format = 'd.m.Y H:i'): string { return date($format, strtotime($date)); } - /** - * Получить flash-сообщения - */ public static function getFlashMessages(): array { $messages = $_SESSION['flash'] ?? []; @@ -131,25 +95,16 @@ class View return $messages; } - /** - * Проверить, авторизован ли пользователь - */ public static function isAuthenticated(): bool { return isset($_SESSION['isLoggedIn']) && $_SESSION['isLoggedIn'] === true; } - /** - * Проверить, является ли пользователь администратором - */ public static function isAdmin(): bool { return self::isAuthenticated() && isset($_SESSION['isAdmin']) && $_SESSION['isAdmin'] === true; } - /** - * Получить данные текущего пользователя - */ public static function currentUser(): ?array { if (!self::isAuthenticated()) { @@ -165,20 +120,13 @@ class View ]; } - /** - * Генерация URL - */ public static function url(string $path): string { return '/' . ltrim($path, '/'); } - /** - * Генерация URL для ассетов - */ public static function asset(string $path): string { return '/assets/' . ltrim($path, '/'); } } - diff --git a/app/Models/Cart.php b/app/Models/Cart.php index 757c37c..cf746a2 100644 --- a/app/Models/Cart.php +++ b/app/Models/Cart.php @@ -4,17 +4,11 @@ namespace App\Models; use App\Core\Model; -/** - * Cart - модель корзины - */ class Cart extends Model { protected string $table = 'cart'; protected string $primaryKey = 'cart_id'; - /** - * Получить корзину пользователя - */ public function getUserCart(int $userId): array { $sql = "SELECT @@ -33,9 +27,6 @@ class Cart extends Model return $this->query($sql, [$userId]); } - /** - * Получить общую сумму корзины - */ public function getCartTotal(int $userId): array { $sql = "SELECT @@ -52,9 +43,6 @@ class Cart extends Model ]; } - /** - * Получить количество товаров в корзине - */ public function getCount(int $userId): int { $sql = "SELECT COALESCE(SUM(quantity), 0) as total FROM {$this->table} WHERE user_id = ?"; @@ -62,19 +50,14 @@ class Cart extends Model return (int) $result['total']; } - /** - * Добавить товар в корзину - */ public function addItem(int $userId, int $productId, int $quantity = 1): bool { - // Проверяем, есть ли уже этот товар в корзине $existing = $this->findWhere([ 'user_id' => $userId, 'product_id' => $productId ]); if ($existing) { - // Увеличиваем количество $newQuantity = $existing['quantity'] + $quantity; return $this->update($existing['cart_id'], [ 'quantity' => $newQuantity, @@ -82,7 +65,6 @@ class Cart extends Model ]); } - // Добавляем новую запись return $this->create([ 'user_id' => $userId, 'product_id' => $productId, @@ -90,9 +72,6 @@ class Cart extends Model ]) !== null; } - /** - * Обновить количество товара в корзине - */ public function updateQuantity(int $userId, int $productId, int $quantity): bool { $sql = "UPDATE {$this->table} @@ -101,27 +80,18 @@ class Cart extends Model return $this->execute($sql, [$quantity, $userId, $productId]); } - /** - * Удалить товар из корзины - */ public function removeItem(int $userId, int $productId): bool { $sql = "DELETE FROM {$this->table} WHERE user_id = ? AND product_id = ?"; return $this->execute($sql, [$userId, $productId]); } - /** - * Очистить корзину пользователя - */ public function clearCart(int $userId): bool { $sql = "DELETE FROM {$this->table} WHERE user_id = ?"; return $this->execute($sql, [$userId]); } - /** - * Проверить, есть ли товар в корзине - */ public function hasItem(int $userId, int $productId): bool { $item = $this->findWhere([ @@ -131,9 +101,6 @@ class Cart extends Model return $item !== null; } - /** - * Получить товар из корзины - */ public function getItem(int $userId, int $productId): ?array { return $this->findWhere([ @@ -142,4 +109,3 @@ class Cart extends Model ]); } } - diff --git a/app/Models/Category.php b/app/Models/Category.php index 54d659d..80d2bed 100644 --- a/app/Models/Category.php +++ b/app/Models/Category.php @@ -4,17 +4,11 @@ namespace App\Models; use App\Core\Model; -/** - * Category - модель категории товаров - */ class Category extends Model { protected string $table = 'categories'; protected string $primaryKey = 'category_id'; - /** - * Получить активные категории - */ public function getActive(): array { $sql = "SELECT * FROM {$this->table} @@ -23,9 +17,6 @@ class Category extends Model return $this->query($sql); } - /** - * Получить родительские категории (без parent_id) - */ public function getParent(): array { $sql = "SELECT * FROM {$this->table} @@ -34,9 +25,6 @@ class Category extends Model return $this->query($sql); } - /** - * Получить подкатегории - */ public function getChildren(int $parentId): array { $sql = "SELECT * FROM {$this->table} @@ -45,17 +33,11 @@ class Category extends Model return $this->query($sql, [$parentId]); } - /** - * Получить категорию по slug - */ public function findBySlug(string $slug): ?array { return $this->findWhere(['slug' => $slug]); } - /** - * Получить все категории с количеством товаров - */ public function getAllWithProductCount(): array { $sql = "SELECT c1.*, c2.name as parent_name, @@ -66,9 +48,6 @@ class Category extends Model return $this->query($sql); } - /** - * Создать категорию - */ public function createCategory(array $data): ?int { $slug = $this->generateSlug($data['name']); @@ -83,9 +62,6 @@ class Category extends Model ]); } - /** - * Обновить категорию - */ public function updateCategory(int $id, array $data): bool { $updateData = [ @@ -101,23 +77,17 @@ class Category extends Model return $this->update($id, $updateData); } - /** - * Безопасное удаление категории - */ public function safeDelete(int $id): array { - // Проверяем наличие товаров $sql = "SELECT COUNT(*) as cnt FROM products WHERE category_id = ?"; $result = $this->queryOne($sql, [$id]); $productCount = (int) $result['cnt']; - // Проверяем наличие дочерних категорий $sql = "SELECT COUNT(*) as cnt FROM {$this->table} WHERE parent_id = ?"; $result = $this->queryOne($sql, [$id]); $childCount = (int) $result['cnt']; if ($productCount > 0 || $childCount > 0) { - // Скрываем вместо удаления $this->update($id, ['is_active' => false]); return [ 'deleted' => false, @@ -126,19 +96,14 @@ class Category extends Model ]; } - // Удаляем полностью $this->delete($id); return ['deleted' => true, 'hidden' => false]; } - /** - * Генерация slug из названия - */ private function generateSlug(string $name): string { $slug = mb_strtolower($name); - // Транслитерация $transliteration = [ 'а' => 'a', 'б' => 'b', 'в' => 'v', 'г' => 'g', 'д' => 'd', 'е' => 'e', 'ё' => 'yo', 'ж' => 'zh', 'з' => 'z', 'и' => 'i', @@ -156,4 +121,3 @@ class Category extends Model return $slug; } } - diff --git a/app/Models/Order.php b/app/Models/Order.php index deb31bc..f2cbfe2 100644 --- a/app/Models/Order.php +++ b/app/Models/Order.php @@ -5,17 +5,11 @@ namespace App\Models; use App\Core\Model; use App\Core\Database; -/** - * Order - модель заказа - */ class Order extends Model { protected string $table = 'orders'; protected string $primaryKey = 'order_id'; - /** - * Создать заказ из корзины - */ public function createFromCart(int $userId, array $cartItems, array $orderData): ?array { $db = Database::getInstance(); @@ -23,7 +17,6 @@ class Order extends Model try { $db->beginTransaction(); - // Считаем итоговую сумму $subtotal = 0; foreach ($cartItems as $item) { $subtotal += $item['price'] * $item['quantity']; @@ -33,10 +26,8 @@ class Order extends Model $deliveryPrice = (float) ($orderData['delivery_price'] ?? 2000); $finalAmount = $subtotal - $discountAmount + $deliveryPrice; - // Генерируем номер заказа $orderNumber = 'ORD-' . date('Ymd-His') . '-' . rand(1000, 9999); - // Создаем заказ $orderId = $this->create([ 'user_id' => $userId, 'order_number' => $orderNumber, @@ -61,18 +52,15 @@ class Order extends Model throw new \Exception('Не удалось создать заказ'); } - // Добавляем товары в заказ foreach ($cartItems as $item) { $this->addOrderItem($orderId, $item); } - // Уменьшаем количество товаров на складе $productModel = new Product(); foreach ($cartItems as $item) { $productModel->decreaseStock($item['product_id'], $item['quantity']); } - // Очищаем корзину $cartModel = new Cart(); $cartModel->clearCart($userId); @@ -90,9 +78,6 @@ class Order extends Model } } - /** - * Добавить товар в заказ - */ private function addOrderItem(int $orderId, array $item): void { $sql = "INSERT INTO order_items @@ -111,9 +96,6 @@ class Order extends Model ]); } - /** - * Получить заказ с подробностями - */ public function getWithDetails(int $orderId): ?array { $sql = "SELECT o.*, u.email as user_email, u.full_name as user_full_name @@ -130,9 +112,6 @@ class Order extends Model return $order; } - /** - * Получить товары заказа - */ public function getOrderItems(int $orderId): array { $sql = "SELECT oi.*, p.image_url @@ -142,9 +121,6 @@ class Order extends Model return $this->query($sql, [$orderId]); } - /** - * Получить заказы пользователя - */ public function getUserOrders(int $userId, int $limit = 50): array { $sql = "SELECT * FROM {$this->table} @@ -154,9 +130,6 @@ class Order extends Model return $this->query($sql, [$userId, $limit]); } - /** - * Получить все заказы для админки - */ public function getAllForAdmin(int $limit = 50): array { $sql = "SELECT o.*, u.email as user_email @@ -167,9 +140,6 @@ class Order extends Model return $this->query($sql, [$limit]); } - /** - * Обновить статус заказа - */ public function updateStatus(int $orderId, string $status): bool { $updateData = [ @@ -184,25 +154,19 @@ class Order extends Model return $this->update($orderId, $updateData); } - /** - * Получить статистику заказов - */ public function getStats(): array { $stats = []; - // Общее количество заказов $result = $this->queryOne("SELECT COUNT(*) as cnt FROM {$this->table}"); $stats['total'] = (int) $result['cnt']; - // Выручка $result = $this->queryOne( "SELECT COALESCE(SUM(final_amount), 0) as revenue FROM {$this->table} WHERE status = 'completed'" ); $stats['revenue'] = (float) $result['revenue']; - // По статусам $statuses = $this->query( "SELECT status, COUNT(*) as cnt FROM {$this->table} GROUP BY status" ); @@ -214,4 +178,3 @@ class Order extends Model return $stats; } } - diff --git a/app/Models/Product.php b/app/Models/Product.php index 3ebf4c7..32ffacf 100644 --- a/app/Models/Product.php +++ b/app/Models/Product.php @@ -4,17 +4,11 @@ namespace App\Models; use App\Core\Model; -/** - * Product - модель товара - */ class Product extends Model { protected string $table = 'products'; protected string $primaryKey = 'product_id'; - /** - * Получить товар с категорией - */ public function findWithCategory(int $id): ?array { $sql = "SELECT p.*, c.name as category_name, c.slug as category_slug @@ -24,9 +18,6 @@ class Product extends Model return $this->queryOne($sql, [$id]); } - /** - * Получить доступные товары - */ public function getAvailable(array $filters = [], int $limit = 50): array { $sql = "SELECT p.*, c.name as category_name @@ -35,13 +26,11 @@ class Product extends Model WHERE p.is_available = TRUE"; $params = []; - // Фильтр по категории if (!empty($filters['category_id'])) { $sql .= " AND p.category_id = ?"; $params[] = $filters['category_id']; } - // Фильтр по цене if (!empty($filters['min_price'])) { $sql .= " AND p.price >= ?"; $params[] = $filters['min_price']; @@ -51,21 +40,18 @@ class Product extends Model $params[] = $filters['max_price']; } - // Фильтр по цветам if (!empty($filters['colors']) && is_array($filters['colors'])) { $placeholders = implode(',', array_fill(0, count($filters['colors']), '?')); $sql .= " AND p.color IN ({$placeholders})"; $params = array_merge($params, $filters['colors']); } - // Фильтр по материалам if (!empty($filters['materials']) && is_array($filters['materials'])) { $placeholders = implode(',', array_fill(0, count($filters['materials']), '?')); $sql .= " AND p.material IN ({$placeholders})"; $params = array_merge($params, $filters['materials']); } - // Поиск по названию/описанию if (!empty($filters['search'])) { $sql .= " AND (p.name ILIKE ? OR p.description ILIKE ?)"; $search = '%' . $filters['search'] . '%'; @@ -79,9 +65,6 @@ class Product extends Model return $this->query($sql, $params); } - /** - * Получить все товары (включая недоступные) для админки - */ public function getAllForAdmin(bool $showAll = true): array { $sql = "SELECT p.*, c.name as category_name @@ -97,9 +80,6 @@ class Product extends Model return $this->query($sql); } - /** - * Получить похожие товары - */ public function getSimilar(int $productId, int $categoryId, int $limit = 3): array { $sql = "SELECT * FROM {$this->table} @@ -111,9 +91,6 @@ class Product extends Model return $this->query($sql, [$categoryId, $productId, $limit]); } - /** - * Получить доступные цвета - */ public function getAvailableColors(): array { $sql = "SELECT DISTINCT color FROM {$this->table} @@ -123,9 +100,6 @@ class Product extends Model return array_column($result, 'color'); } - /** - * Получить доступные материалы - */ public function getAvailableMaterials(): array { $sql = "SELECT DISTINCT material FROM {$this->table} @@ -135,9 +109,6 @@ class Product extends Model return array_column($result, 'material'); } - /** - * Создать товар - */ public function createProduct(array $data): ?int { $slug = $this->generateSlug($data['name']); @@ -161,9 +132,6 @@ class Product extends Model ]); } - /** - * Обновить товар - */ public function updateProduct(int $id, array $data): bool { $updateData = [ @@ -183,9 +151,6 @@ class Product extends Model return $this->update($id, $updateData); } - /** - * Уменьшить количество товара на складе - */ public function decreaseStock(int $productId, int $quantity): bool { $sql = "UPDATE {$this->table} @@ -195,18 +160,12 @@ class Product extends Model return $this->execute($sql, [$quantity, $productId]); } - /** - * Проверить наличие товара - */ public function checkStock(int $productId, int $quantity): bool { $product = $this->find($productId); return $product && $product['is_available'] && $product['stock_quantity'] >= $quantity; } - /** - * Генерация slug - */ private function generateSlug(string $name): string { $slug = mb_strtolower($name); @@ -227,13 +186,9 @@ class Product extends Model return trim($slug, '-'); } - /** - * Генерация артикула - */ private function generateSku(string $name): string { $prefix = strtoupper(substr(preg_replace('/[^a-zA-Z0-9]/', '', $name), 0, 6)); return 'PROD-' . $prefix . '-' . rand(100, 999); } } - diff --git a/app/Models/User.php b/app/Models/User.php index baec07e..4d4437d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -4,46 +4,30 @@ namespace App\Models; use App\Core\Model; -/** - * User - модель пользователя - */ class User extends Model { protected string $table = 'users'; protected string $primaryKey = 'user_id'; - /** - * Найти пользователя по email - */ public function findByEmail(string $email): ?array { return $this->findWhere(['email' => $email]); } - /** - * Проверить пароль пользователя - */ public function verifyPassword(string $password, string $hash): bool { return password_verify($password, $hash); } - /** - * Хешировать пароль - */ public function hashPassword(string $password): string { return password_hash($password, PASSWORD_DEFAULT); } - /** - * Создать нового пользователя - */ public function register(array $data): ?int { $config = require dirname(__DIR__, 2) . '/config/app.php'; - // Проверяем, является ли email администраторским $isAdmin = in_array(strtolower($data['email']), $config['admin_emails'] ?? []); return $this->create([ @@ -52,14 +36,11 @@ class User extends Model 'full_name' => $data['full_name'], 'phone' => $data['phone'] ?? null, 'city' => $data['city'] ?? null, - 'is_admin' => $isAdmin, - 'is_active' => true + 'is_admin' => $isAdmin ? 'true' : 'false', + 'is_active' => 'true' ]); } - /** - * Авторизация пользователя - */ public function authenticate(string $email, string $password): ?array { $user = $this->findByEmail($email); @@ -76,7 +57,6 @@ class User extends Model return null; } - // Обновляем время последнего входа $this->update($user['user_id'], [ 'last_login' => date('Y-m-d H:i:s') ]); @@ -84,9 +64,6 @@ class User extends Model return $user; } - /** - * Получить активных пользователей - */ public function getActive(int $limit = 50): array { $sql = "SELECT * FROM {$this->table} @@ -96,9 +73,6 @@ class User extends Model return $this->query($sql, [$limit]); } - /** - * Получить всех пользователей с пагинацией - */ public function getAllPaginated(int $limit = 50, int $offset = 0): array { $sql = "SELECT * FROM {$this->table} @@ -107,18 +81,12 @@ class User extends Model return $this->query($sql, [$limit, $offset]); } - /** - * Проверить существование email - */ public function emailExists(string $email): bool { $user = $this->findByEmail($email); return $user !== null; } - /** - * Обновить профиль пользователя - */ public function updateProfile(int $userId, array $data): bool { $allowedFields = ['full_name', 'phone', 'city']; @@ -128,9 +96,6 @@ class User extends Model return $this->update($userId, $updateData); } - /** - * Изменить пароль - */ public function changePassword(int $userId, string $newPassword): bool { return $this->update($userId, [ @@ -139,9 +104,6 @@ class User extends Model ]); } - /** - * Заблокировать/разблокировать пользователя - */ public function setActive(int $userId, bool $active): bool { return $this->update($userId, [ @@ -150,4 +112,3 @@ class User extends Model ]); } } - diff --git a/app/Views/admin/categories/form.php b/app/Views/admin/categories/form.php index 1c74a14..f64c6c2 100644 --- a/app/Views/admin/categories/form.php +++ b/app/Views/admin/categories/form.php @@ -2,12 +2,12 @@

- + Назад к списку
-
+

Управление категориями

- + Добавить категорию
@@ -42,10 +42,10 @@
- + -