E-Commerce Platform with Content-Based Recommendations

Complete Full-Stack E-Commerce System with PHP, MySQL, JavaScript


PROJECT OVERVIEW

A fully functional e-commerce platform with content-based product recommendation system that suggests products based on user browsing history, purchase behavior, and product attributes.

Core Features

User Features

  • ✅ User registration and authentication
  • ✅ Product browsing and search
  • ✅ Shopping cart management
  • ✅ Order placement and tracking
  • ✅ Wishlist and saved items
  • ✅ Product reviews and ratings
  • ✅ Multiple payment methods
  • ✅ Order history
  • ✅ Address management

Seller Features

  • ✅ Seller registration and dashboard
  • ✅ Product management (add/edit/delete)
  • ✅ Inventory management
  • ✅ Order management
  • ✅ Sales analytics
  • ✅ Customer reviews management
  • ✅ Shipping settings

Admin Features

  • ✅ User management
  • ✅ Seller approval
  • ✅ Category management
  • ✅ Order monitoring
  • ✅ Platform analytics
  • ✅ Discount/coupon management
  • ✅ Report generation

Content-Based Recommendations

  • ✅ Similar products based on attributes
  • ✅ "Customers who bought this also bought"
  • ✅ Personalized product suggestions
  • ✅ Recently viewed items
  • ✅ Trending products
  • ✅ Category-based recommendations
  • ✅ Price range suggestions

DATABASE SCHEMA

CREATE DATABASE ecommerce_platform; USE ecommerce_platform; -- Users table CREATE TABLE users ( id INT PRIMARY KEY AUTO_INCREMENT, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, full_name VARCHAR(100), profile_pic VARCHAR(255), phone VARCHAR(20), user_type ENUM('customer', 'seller', 'admin') DEFAULT 'customer', is_active BOOLEAN DEFAULT TRUE, is_verified BOOLEAN DEFAULT FALSE, email_verified BOOLEAN DEFAULT FALSE, last_login TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_email (email), INDEX idx_user_type (user_type), FULLTEXT INDEX ft_search (username, full_name, email) ); -- User addresses CREATE TABLE addresses ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, address_type ENUM('shipping', 'billing') DEFAULT 'shipping', recipient_name VARCHAR(100), address_line1 VARCHAR(255) NOT NULL, address_line2 VARCHAR(255), city VARCHAR(100) NOT NULL, state VARCHAR(100), postal_code VARCHAR(20), country VARCHAR(100) DEFAULT 'USA', phone VARCHAR(20), is_default BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id) ); -- Categories CREATE TABLE categories ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, slug VARCHAR(100) UNIQUE NOT NULL, description TEXT, parent_id INT DEFAULT NULL, image_url VARCHAR(255), is_active BOOLEAN DEFAULT TRUE, sort_order INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (parent_id) REFERENCES categories(id) ON DELETE CASCADE, INDEX idx_parent (parent_id), INDEX idx_active (is_active) ); -- Brands CREATE TABLE brands ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) UNIQUE NOT NULL, slug VARCHAR(100) UNIQUE NOT NULL, logo_url VARCHAR(255), description TEXT, website VARCHAR(255), is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Products CREATE TABLE products ( id INT PRIMARY KEY AUTO_INCREMENT, seller_id INT NOT NULL, category_id INT, brand_id INT, name VARCHAR(255) NOT NULL, slug VARCHAR(255) UNIQUE NOT NULL, description TEXT, short_description VARCHAR(500), price DECIMAL(10,2) NOT NULL, compare_at_price DECIMAL(10,2), cost_price DECIMAL(10,2), sku VARCHAR(100) UNIQUE, barcode VARCHAR(100), quantity INT DEFAULT 0, low_stock_threshold INT DEFAULT 5, weight DECIMAL(8,2), weight_unit ENUM('kg', 'lb', 'g') DEFAULT 'kg', dimensions VARCHAR(100), is_featured BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, is_taxable BOOLEAN DEFAULT TRUE, views_count INT DEFAULT 0, sold_count INT DEFAULT 0, rating_avg DECIMAL(3,2) DEFAULT 0, review_count INT DEFAULT 0, meta_title VARCHAR(255), meta_description TEXT, meta_keywords TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE SET NULL, FOREIGN KEY (brand_id) REFERENCES brands(id) ON DELETE SET NULL, INDEX idx_category (category_id), INDEX idx_brand (brand_id), INDEX idx_seller (seller_id), INDEX idx_price (price), INDEX idx_featured (is_featured), INDEX idx_rating (rating_avg), INDEX idx_sold (sold_count), FULLTEXT INDEX ft_product (name, description, short_description) ); -- Product images CREATE TABLE product_images ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, image_url VARCHAR(255) NOT NULL, is_primary BOOLEAN DEFAULT FALSE, sort_order INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, INDEX idx_product (product_id) ); -- Product attributes (for filtering and recommendations) CREATE TABLE product_attributes ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, attribute_name VARCHAR(100) NOT NULL, attribute_value VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, INDEX idx_product (product_id), INDEX idx_name (attribute_name) ); -- Product tags CREATE TABLE tags ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(50) UNIQUE NOT NULL, slug VARCHAR(50) UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Product tag relationships CREATE TABLE product_tags ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, tag_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, FOREIGN KEY (tag_id) REFERENCES tags(id) ON DELETE CASCADE, UNIQUE KEY unique_product_tag (product_id, tag_id) ); -- Product reviews CREATE TABLE reviews ( id INT PRIMARY KEY AUTO_INCREMENT, product_id INT NOT NULL, user_id INT NOT NULL, rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5), title VARCHAR(255), content TEXT, is_verified_purchase BOOLEAN DEFAULT FALSE, is_active BOOLEAN DEFAULT TRUE, helpful_count INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_user_product (user_id, product_id), INDEX idx_product (product_id), INDEX idx_rating (rating) ); -- Review helpfulness CREATE TABLE review_helpful ( id INT PRIMARY KEY AUTO_INCREMENT, review_id INT NOT NULL, user_id INT NOT NULL, is_helpful BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (review_id) REFERENCES reviews(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_review_user (review_id, user_id) ); -- Shopping cart CREATE TABLE carts ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, session_id VARCHAR(255), created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_session (session_id) ); -- Cart items CREATE TABLE cart_items ( id INT PRIMARY KEY AUTO_INCREMENT, cart_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL DEFAULT 1, price DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (cart_id) REFERENCES carts(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, UNIQUE KEY unique_cart_product (cart_id, product_id) ); -- Wishlist CREATE TABLE wishlists ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, name VARCHAR(100) DEFAULT 'Default Wishlist', is_public BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, INDEX idx_user (user_id) ); -- Wishlist items CREATE TABLE wishlist_items ( id INT PRIMARY KEY AUTO_INCREMENT, wishlist_id INT NOT NULL, product_id INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (wishlist_id) REFERENCES wishlists(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, UNIQUE KEY unique_wishlist_product (wishlist_id, product_id) ); -- Orders CREATE TABLE orders ( id INT PRIMARY KEY AUTO_INCREMENT, order_number VARCHAR(50) UNIQUE NOT NULL, user_id INT NOT NULL, seller_id INT, shipping_address_id INT, billing_address_id INT, subtotal DECIMAL(10,2) NOT NULL, shipping_cost DECIMAL(10,2) DEFAULT 0, tax_amount DECIMAL(10,2) DEFAULT 0, discount_amount DECIMAL(10,2) DEFAULT 0, total_amount DECIMAL(10,2) NOT NULL, payment_method ENUM('credit_card', 'debit_card', 'paypal', 'bank_transfer', 'cod') NOT NULL, payment_status ENUM('pending', 'processing', 'completed', 'failed', 'refunded') DEFAULT 'pending', order_status ENUM('pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded') DEFAULT 'pending', shipping_method VARCHAR(100), tracking_number VARCHAR(100), notes TEXT, placed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, paid_at TIMESTAMP NULL, shipped_at TIMESTAMP NULL, delivered_at TIMESTAMP NULL, cancelled_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE SET NULL, FOREIGN KEY (shipping_address_id) REFERENCES addresses(id) ON DELETE SET NULL, FOREIGN KEY (billing_address_id) REFERENCES addresses(id) ON DELETE SET NULL, INDEX idx_user (user_id), INDEX idx_seller (seller_id), INDEX idx_order_number (order_number), INDEX idx_status (order_status), INDEX idx_placed (placed_at) ); -- Order items CREATE TABLE order_items ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, product_id INT NOT NULL, seller_id INT, product_name VARCHAR(255) NOT NULL, product_sku VARCHAR(100), quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, total DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE SET NULL, FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE SET NULL, INDEX idx_order (order_id), INDEX idx_seller (seller_id) ); -- Payments CREATE TABLE payments ( id INT PRIMARY KEY AUTO_INCREMENT, order_id INT NOT NULL, transaction_id VARCHAR(255), payment_method VARCHAR(50) NOT NULL, amount DECIMAL(10,2) NOT NULL, status ENUM('pending', 'processing', 'completed', 'failed', 'refunded') DEFAULT 'pending', payment_data JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE CASCADE, INDEX idx_order (order_id), INDEX idx_transaction (transaction_id) ); -- User product interactions (for recommendations) CREATE TABLE user_activities ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, activity_type ENUM('view', 'cart_add', 'cart_remove', 'purchase', 'wishlist_add', 'review', 'search') NOT NULL, duration INT, -- Time spent viewing in seconds metadata JSON, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_product (product_id), INDEX idx_type (activity_type), INDEX idx_created (created_at) ); -- User product ratings (implicit feedback) CREATE TABLE user_ratings ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, rating FLOAT DEFAULT 0, -- Implicit rating based on behavior last_interaction TIMESTAMP DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, UNIQUE KEY unique_user_product (user_id, product_id), INDEX idx_user (user_id), INDEX idx_rating (rating) ); -- Product similarity matrix (for recommendations) CREATE TABLE product_similarity ( id INT PRIMARY KEY AUTO_INCREMENT, product_id_1 INT NOT NULL, product_id_2 INT NOT NULL, similarity_score DECIMAL(5,4) NOT NULL, based_on VARCHAR(50), -- 'category', 'brand', 'attributes', 'purchases' created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (product_id_1) REFERENCES products(id) ON DELETE CASCADE, FOREIGN KEY (product_id_2) REFERENCES products(id) ON DELETE CASCADE, UNIQUE KEY unique_product_pair (product_id_1, product_id_2), INDEX idx_product_1 (product_id_1), INDEX idx_product_2 (product_id_2), INDEX idx_score (similarity_score) ); -- Product recommendations cache CREATE TABLE product_recommendations ( id INT PRIMARY KEY AUTO_INCREMENT, user_id INT NOT NULL, product_id INT NOT NULL, score DECIMAL(5,4) NOT NULL, reason VARCHAR(50), -- 'similar', 'popular', 'trending', 'purchased_together' is_viewed BOOLEAN DEFAULT FALSE, is_clicked BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, expires_at TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, INDEX idx_user (user_id), INDEX idx_product (product_id), INDEX idx_score (score), INDEX idx_expires (expires_at) ); -- Coupons and discounts CREATE TABLE coupons ( id INT PRIMARY KEY AUTO_INCREMENT, code VARCHAR(50) UNIQUE NOT NULL, description TEXT, discount_type ENUM('percentage', 'fixed') NOT NULL, discount_value DECIMAL(10,2) NOT NULL, min_order_amount DECIMAL(10,2), max_discount_amount DECIMAL(10,2), usage_limit INT, usage_per_user INT, used_count INT DEFAULT 0, start_date TIMESTAMP NULL, end_date TIMESTAMP NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, INDEX idx_code (code), INDEX idx_active (is_active), INDEX idx_dates (start_date, end_date) ); -- Coupon usage CREATE TABLE coupon_usage ( id INT PRIMARY KEY AUTO_INCREMENT, coupon_id INT NOT NULL, user_id INT NOT NULL, order_id INT, used_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (coupon_id) REFERENCES coupons(id) ON DELETE CASCADE, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (order_id) REFERENCES orders(id) ON DELETE SET NULL, INDEX idx_coupon (coupon_id), INDEX idx_user (user_id) ); -- Seller settings CREATE TABLE seller_settings ( id INT PRIMARY KEY AUTO_INCREMENT, seller_id INT NOT NULL, store_name VARCHAR(255), store_logo VARCHAR(255), store_description TEXT, return_policy TEXT, shipping_policy TEXT, payment_info JSON, commission_rate DECIMAL(5,2) DEFAULT 0, is_approved BOOLEAN DEFAULT FALSE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE CASCADE, UNIQUE KEY unique_seller (seller_id) ); -- Seller products CREATE TABLE seller_products ( id INT PRIMARY KEY AUTO_INCREMENT, seller_id INT NOT NULL, product_id INT NOT NULL, status ENUM('active', 'inactive', 'pending') DEFAULT 'pending', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (seller_id) REFERENCES users(id) ON DELETE CASCADE, FOREIGN KEY (product_id) REFERENCES products(id) ON DELETE CASCADE, UNIQUE KEY unique_seller_product (seller_id, product_id) ); -- Shipping zones CREATE TABLE shipping_zones ( id INT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(100) NOT NULL, countries JSON, states JSON, cities JSON, zip_codes JSON, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Shipping rates CREATE TABLE shipping_rates ( id INT PRIMARY KEY AUTO_INCREMENT, zone_id INT NOT NULL, name VARCHAR(100) NOT NULL, rate_type ENUM('flat', 'weight_based', 'price_based') NOT NULL, min_value DECIMAL(10,2), max_value DECIMAL(10,2), cost DECIMAL(10,2) NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (zone_id) REFERENCES shipping_zones(id) ON DELETE CASCADE ); -- Site settings CREATE TABLE settings ( id INT PRIMARY KEY AUTO_INCREMENT, setting_key VARCHAR(100) UNIQUE NOT NULL, setting_value TEXT, description TEXT, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ); -- Insert default settings INSERT INTO settings (setting_key, setting_value, description) VALUES ('site_name', 'ShopEase', 'Website name'), ('site_description', 'Your one-stop shop for everything', 'Site description'), ('currency', 'USD', 'Default currency'), ('currency_symbol', '$', 'Currency symbol'), ('tax_rate', '10.00', 'Default tax rate percentage'), ('shipping_cost', '5.00', 'Default shipping cost'), ('free_shipping_threshold', '50.00', 'Free shipping minimum order'), ('max_upload_size', '10', 'Maximum upload size in MB'), ('products_per_page', '24', 'Number of products per page'), ('recommendation_limit', '20', 'Number of recommendations to generate'), ('trending_days', '7', 'Days to consider for trending'), ('maintenance_mode', '0', 'Maintenance mode status'), ('registration_enabled', '1', 'Allow new registrations'); -- Insert sample categories INSERT INTO categories (name, slug, description) VALUES ('Electronics', 'electronics', 'Electronic devices and gadgets'), ('Clothing', 'clothing', 'Fashion and apparel'), ('Books', 'books', 'Books and publications'), ('Home & Garden', 'home-garden', 'Home decor and garden supplies'), ('Sports & Outdoors', 'sports-outdoors', 'Sports equipment and outdoor gear'), ('Toys & Games', 'toys-games', 'Toys, games, and entertainment'), ('Health & Beauty', 'health-beauty', 'Health and beauty products'), ('Automotive', 'automotive', 'Car parts and accessories'); -- Insert sample brands INSERT INTO brands (name, slug) VALUES ('Apple', 'apple'), ('Samsung', 'samsung'), ('Nike', 'nike'), ('Adidas', 'adidas'), ('Sony', 'sony'), ('LG', 'lg'), ('Microsoft', 'microsoft'), ('Dell', 'dell');

BACKEND IMPLEMENTATION

config.php

<?php session_start(); // Database configuration define('DB_HOST', 'localhost'); define('DB_NAME', 'ecommerce_platform'); define('DB_USER', 'root'); define('DB_PASS', ''); // Site configuration define('SITE_URL', 'http://localhost/ecommerce'); define('SITE_NAME', 'ShopEase'); define('UPLOAD_DIR', __DIR__ . '/uploads/'); define('MAX_FILE_SIZE', 10 * 1024 * 1024); // 10MB define('TIMEZONE', 'UTC'); define('DEBUG_MODE', true); // Currency settings define('CURRENCY', 'USD'); define('CURRENCY_SYMBOL', '$'); // Pagination define('PRODUCTS_PER_PAGE', 24); define('RECOMMENDATION_LIMIT', 20); // Tax and shipping define('DEFAULT_TAX_RATE', 10.00); define('DEFAULT_SHIPPING_COST', 5.00); define('FREE_SHIPPING_THRESHOLD', 50.00); // Set timezone date_default_timezone_set(TIMEZONE); // Error reporting if (DEBUG_MODE) { error_reporting(E_ALL); ini_set('display_errors', 1); } else { error_reporting(0); ini_set('display_errors', 0); } // Database connection try { $pdo = new PDO( "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4", DB_USER, DB_PASS, [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false ] ); } catch(PDOException $e) { die("Connection failed: " . $e->getMessage()); } // Create upload directories $upload_dirs = [ UPLOAD_DIR, UPLOAD_DIR . 'products/', UPLOAD_DIR . 'categories/', UPLOAD_DIR . 'brands/', UPLOAD_DIR . 'profiles/' ]; foreach ($upload_dirs as $dir) { if (!file_exists($dir)) { mkdir($dir, 0777, true); } } // Include helper functions require_once 'helpers.php'; // Autoload classes spl_autoload_register(function ($class) { $file = __DIR__ . '/classes/' . $class . '.php'; if (file_exists($file)) { require_once $file; } }); // Check if user is logged in function isLoggedIn() { return isset($_SESSION['user_id']); } // Get current user ID function getCurrentUserId() { return $_SESSION['user_id'] ?? null; } // Get current user type function getUserType() { return $_SESSION['user_type'] ?? null; } // Get current user data function getCurrentUser() { global $pdo; if (!isLoggedIn()) return null; static $user = null; if ($user === null) { $stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?"); $stmt->execute([$_SESSION['user_id']]); $user = $stmt->fetch(); } return $user; } // Check if user is seller function isSeller() { return getUserType() === 'seller'; } // Check if user is admin function isAdmin() { return getUserType() === 'admin'; } // Redirect function function redirect($url) { header("Location: " . SITE_URL . $url); exit; } // JSON response function function jsonResponse($data, $status = 200) { http_response_code($status); header('Content-Type: application/json'); echo json_encode($data); exit; } ?>

helpers.php

<?php // Helper functions // Sanitize input function sanitize($input) { return htmlspecialchars(trim($input), ENT_QUOTES, 'UTF-8'); } // Generate random string function generateRandomString($length = 32) { return bin2hex(random_bytes($length / 2)); } // Generate slug function createSlug($string) { $string = strtolower($string); $string = preg_replace('/[^a-z0-9-]/', '-', $string); $string = preg_replace('/-+/', '-', $string); return trim($string, '-'); } // Format price function formatPrice($price) { return CURRENCY_SYMBOL . number_format($price, 2); } // Calculate discount percentage function getDiscountPercentage($price, $comparePrice) { if ($comparePrice <= 0) return 0; return round((($comparePrice - $price) / $comparePrice) * 100); } // Format date function formatDate($date, $format = 'M j, Y') { return date($format, strtotime($date)); } // Time ago function function timeAgo($datetime) { $time = strtotime($datetime); $now = time(); $diff = $now - $time; if ($diff < 60) { return $diff . ' seconds ago'; } elseif ($diff < 3600) { $mins = floor($diff / 60); return $mins . ' minute' . ($mins > 1 ? 's' : '') . ' ago'; } elseif ($diff < 86400) { $hours = floor($diff / 3600); return $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago'; } elseif ($diff < 2592000) { $days = floor($diff / 86400); return $days . ' day' . ($days > 1 ? 's' : '') . ' ago'; } elseif ($diff < 31536000) { $months = floor($diff / 2592000); return $months . ' month' . ($months > 1 ? 's' : '') . ' ago'; } else { $years = floor($diff / 31536000); return $years . ' year' . ($years > 1 ? 's' : '') . ' ago'; } } // Upload file function uploadFile($file, $folder, $allowedTypes = ['jpg', 'jpeg', 'png', 'gif']) { if ($file['error'] !== UPLOAD_ERR_OK) { return ['success' => false, 'message' => 'Upload failed']; } $extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION)); if (!in_array($extension, $allowedTypes)) { return ['success' => false, 'message' => 'File type not allowed']; } if ($file['size'] > MAX_FILE_SIZE) { return ['success' => false, 'message' => 'File too large']; } $filename = uniqid() . '_' . time() . '.' . $extension; $filepath = UPLOAD_DIR . $folder . '/' . $filename; if (move_uploaded_file($file['tmp_name'], $filepath)) { return [ 'success' => true, 'filename' => $filename, 'path' => 'uploads/' . $folder . '/' . $filename ]; } return ['success' => false, 'message' => 'Failed to save file']; } // Get file URL function getFileUrl($path) { if (empty($path)) return null; return SITE_URL . '/' . $path; } // Get product image URL function getProductImage($product, $index = 0) { if (!empty($product['images'])) { $images = json_decode($product['images'], true); if (!empty($images[$index])) { return getFileUrl($images[$index]); } } return SITE_URL . '/assets/images/no-image.png'; } // Get rating stars HTML function getRatingStars($rating) { $stars = ''; for ($i = 1; $i <= 5; $i++) { if ($i <= $rating) { $stars .= '<i class="fas fa-star text-yellow-400"></i>'; } elseif ($i - 0.5 <= $rating) { $stars .= '<i class="fas fa-star-half-alt text-yellow-400"></i>'; } else { $stars .= '<i class="far fa-star text-yellow-400"></i>'; } } return $stars; } // Generate order number function generateOrderNumber() { return 'ORD-' . strtoupper(uniqid()) . '-' . date('Ymd'); } // Calculate cart total function calculateCartTotal($items) { $subtotal = 0; foreach ($items as $item) { $subtotal += $item['price'] * $item['quantity']; } $tax = $subtotal * (DEFAULT_TAX_RATE / 100); $shipping = $subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : DEFAULT_SHIPPING_COST; $total = $subtotal + $tax + $shipping; return [ 'subtotal' => $subtotal, 'tax' => $tax, 'shipping' => $shipping, 'total' => $total ]; } // Get cart from session function getCart() { if (!isset($_SESSION['cart'])) { $_SESSION['cart'] = []; } return $_SESSION['cart']; } // Add to cart function addToCart($productId, $quantity = 1) { $cart = getCart(); if (isset($cart[$productId])) { $cart[$productId]['quantity'] += $quantity; } else { $cart[$productId] = [ 'product_id' => $productId, 'quantity' => $quantity ]; } $_SESSION['cart'] = $cart; return count($cart); } // Remove from cart function removeFromCart($productId) { $cart = getCart(); if (isset($cart[$productId])) { unset($cart[$productId]); $_SESSION['cart'] = $cart; } return count($cart); } // Update cart quantity function updateCartQuantity($productId, $quantity) { $cart = getCart(); if (isset($cart[$productId])) { if ($quantity <= 0) { unset($cart[$productId]); } else { $cart[$productId]['quantity'] = $quantity; } $_SESSION['cart'] = $cart; } return count($cart); } // Clear cart function clearCart() { $_SESSION['cart'] = []; } // Get cart count function getCartCount() { $cart = getCart(); return array_sum(array_column($cart, 'quantity')); } // Log user activity for recommendations function logUserActivity($userId, $productId, $activityType, $metadata = []) { global $pdo; $stmt = $pdo->prepare(" INSERT INTO user_activities (user_id, product_id, activity_type, metadata) VALUES (?, ?, ?, ?) "); $stmt->execute([$userId, $productId, $activityType, json_encode($metadata)]); // Update implicit rating $ratingValue = getImplicitRatingValue($activityType); if ($ratingValue > 0) { $stmt = $pdo->prepare(" INSERT INTO user_ratings (user_id, product_id, rating) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE rating = (rating + ?) / 2, last_interaction = NOW() "); $stmt->execute([$userId, $productId, $ratingValue, $ratingValue]); } } // Get implicit rating value based on activity type function getImplicitRatingValue($activityType) { $ratings = [ 'purchase' => 1.0, 'review' => 0.9, 'wishlist_add' => 0.8, 'cart_add' => 0.7, 'view' => 0.3 ]; return $ratings[$activityType] ?? 0; } // Get similar products based on attributes function getSimilarProducts($productId, $limit = 10) { global $pdo; $stmt = $pdo->prepare(" SELECT p.*, COUNT(*) as similarity_score FROM products p JOIN product_attributes pa1 ON p.id = pa1.product_id JOIN product_attributes pa2 ON pa1.attribute_name = pa2.attribute_name AND pa1.attribute_value = pa2.attribute_value WHERE pa2.product_id = ? AND p.id != ? AND p.is_active = 1 GROUP BY p.id ORDER BY similarity_score DESC, p.sold_count DESC LIMIT ? "); $stmt->execute([$productId, $productId, $limit]); return $stmt->fetchAll(); } // Get frequently bought together products function getFrequentlyBoughtTogether($productId, $limit = 5) { global $pdo; $stmt = $pdo->prepare(" SELECT p.*, COUNT(DISTINCT o.id) as order_count FROM products p JOIN order_items oi1 ON p.id = oi1.product_id JOIN order_items oi2 ON oi1.order_id = oi2.order_id JOIN orders o ON oi1.order_id = o.id WHERE oi2.product_id = ? AND p.id != ? AND p.is_active = 1 GROUP BY p.id ORDER BY order_count DESC LIMIT ? "); $stmt->execute([$productId, $productId, $limit]); return $stmt->fetchAll(); }

classes/ProductRecommender.php (Core Recommendation Algorithm)

<?php class ProductRecommender { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Main recommendation engine public function getRecommendations($userId, $type = 'personalized', $limit = 20) { // Check cache first $cached = $this->getCachedRecommendations($userId, $type, $limit); if ($cached) { return $cached; } switch ($type) { case 'personalized': $recommendations = $this->getPersonalizedRecommendations($userId, $limit); break; case 'similar': $productId = $_GET['product_id'] ?? 0; $recommendations = $this->getSimilarProducts($productId, $limit); break; case 'trending': $recommendations = $this->getTrendingProducts($limit); break; case 'popular': $recommendations = $this->getPopularProducts($limit); break; case 'new': $recommendations = $this->getNewProducts($limit); break; case 'frequently_bought': $productId = $_GET['product_id'] ?? 0; $recommendations = $this->getFrequentlyBoughtTogether($productId, $limit); break; case 'category': $categoryId = $_GET['category_id'] ?? 0; $recommendations = $this->getCategoryRecommendations($userId, $categoryId, $limit); break; default: $recommendations = []; } // Cache recommendations $this->cacheRecommendations($userId, $type, $recommendations); return $recommendations; } // Personalized recommendations based on user behavior private function getPersonalizedRecommendations($userId, $limit) { // Get user's rated products $userRatings = $this->getUserRatings($userId); if (empty($userRatings)) { // Cold start: recommend popular products return $this->getPopularProducts($limit); } // Build user profile based on product attributes $userProfile = $this->buildUserProfile($userRatings); // Get candidate products (exclude purchased/viewed) $candidates = $this->getCandidateProducts($userId); // Score each candidate $recommendations = []; foreach ($candidates as $product) { $score = $this->calculateProductScore($product, $userProfile); if ($score > 0.3) { $recommendations[] = [ 'product' => $product, 'score' => $score, 'reason' => $this->getRecommendationReason($product, $userProfile) ]; } } // Sort by score usort($recommendations, function($a, $b) { return $b['score'] <=> $a['score']; }); return array_slice($recommendations, 0, $limit); } // Build user profile from rated products private function buildUserProfile($userRatings) { $profile = [ 'categories' => [], 'brands' => [], 'price_ranges' => [], 'attributes' => [], 'avg_rating' => 0, 'total_ratings' => count($userRatings) ]; $totalRating = 0; foreach ($userRatings as $rating) { $product = $rating['product']; $ratingValue = $rating['rating']; // Category preferences if ($product['category_id']) { $profile['categories'][$product['category_id']] = ($profile['categories'][$product['category_id']] ?? 0) + $ratingValue; } // Brand preferences if ($product['brand_id']) { $profile['brands'][$product['brand_id']] = ($profile['brands'][$product['brand_id']] ?? 0) + $ratingValue; } // Price range preferences $priceRange = $this->getPriceRange($product['price']); $profile['price_ranges'][$priceRange] = ($profile['price_ranges'][$priceRange] ?? 0) + $ratingValue; // Product attributes $attributes = $this->getProductAttributes($product['id']); foreach ($attributes as $attr) { $key = $attr['attribute_name'] . ':' . $attr['attribute_value']; $profile['attributes'][$key] = ($profile['attributes'][$key] ?? 0) + $ratingValue; } $totalRating += $ratingValue; } // Calculate average rating $profile['avg_rating'] = $totalRating / $profile['total_ratings']; // Normalize preferences foreach ($profile as &$values) { if (is_array($values)) { foreach ($values as &$value) { $value = $value / $totalRating; } } } return $profile; } // Calculate product relevance score private function calculateProductScore($product, $userProfile) { $weights = [ 'category' => 0.30, 'brand' => 0.20, 'price' => 0.15, 'attributes' => 0.25, 'popularity' => 0.10 ]; $scores = [ 'category' => $this->calculateCategoryScore($product, $userProfile), 'brand' => $this->calculateBrandScore($product, $userProfile), 'price' => $this->calculatePriceScore($product, $userProfile), 'attributes' => $this->calculateAttributesScore($product, $userProfile), 'popularity' => $this->calculatePopularityScore($product) ]; $total = 0; foreach ($weights as $factor => $weight) { $total += $scores[$factor] * $weight; } return $total; } // Calculate category match score private function calculateCategoryScore($product, $userProfile) { if (!$product['category_id'] || empty($userProfile['categories'])) { return 0.5; } return $userProfile['categories'][$product['category_id']] ?? 0; } // Calculate brand match score private function calculateBrandScore($product, $userProfile) { if (!$product['brand_id'] || empty($userProfile['brands'])) { return 0.5; } return $userProfile['brands'][$product['brand_id']] ?? 0; } // Calculate price range match score private function calculatePriceScore($product, $userProfile) { if (empty($userProfile['price_ranges'])) { return 0.5; } $priceRange = $this->getPriceRange($product['price']); return $userProfile['price_ranges'][$priceRange] ?? 0.3; } // Calculate attributes match score private function calculateAttributesScore($product, $userProfile) { $attributes = $this->getProductAttributes($product['id']); if (empty($attributes) || empty($userProfile['attributes'])) { return 0; } $score = 0; foreach ($attributes as $attr) { $key = $attr['attribute_name'] . ':' . $attr['attribute_value']; if (isset($userProfile['attributes'][$key])) { $score += $userProfile['attributes'][$key]; } } return $score / count($attributes); } // Calculate popularity score private function calculatePopularityScore($product) { $views = min($product['views_count'] ?? 0, 10000); $sold = min($product['sold_count'] ?? 0, 1000); $rating = $product['rating_avg'] ?? 0; $viewScore = $views / 10000; $soldScore = $sold / 1000; $ratingScore = $rating / 5; return ($viewScore * 0.3 + $soldScore * 0.4 + $ratingScore * 0.3); } // Get price range category private function getPriceRange($price) { if ($price < 25) return 'budget'; if ($price < 50) return 'low'; if ($price < 100) return 'medium'; if ($price < 250) return 'high'; return 'premium'; } // Get user's rated products private function getUserRatings($userId) { $stmt = $this->pdo->prepare(" SELECT ur.*, p.*, c.name as category_name, b.name as brand_name FROM user_ratings ur JOIN products p ON ur.product_id = p.id LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN brands b ON p.brand_id = b.id WHERE ur.user_id = ? ORDER BY ur.rating DESC, ur.last_interaction DESC LIMIT 50 "); $stmt->execute([$userId]); return $stmt->fetchAll(); } // Get product attributes private function getProductAttributes($productId) { $stmt = $this->pdo->prepare(" SELECT * FROM product_attributes WHERE product_id = ? "); $stmt->execute([$productId]); return $stmt->fetchAll(); } // Get candidate products (exclude those user has interacted with) private function getCandidateProducts($userId) { $stmt = $this->pdo->prepare(" SELECT p.*, c.name as category_name, b.name as brand_name FROM products p LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN brands b ON p.brand_id = b.id WHERE p.is_active = 1 AND p.id NOT IN ( SELECT product_id FROM user_activities WHERE user_id = ? UNION SELECT product_id FROM user_ratings WHERE user_id = ? UNION SELECT product_id FROM order_items oi JOIN orders o ON oi.order_id = o.id WHERE o.user_id = ? ) ORDER BY p.created_at DESC LIMIT 200 "); $stmt->execute([$userId, $userId, $userId]); return $stmt->fetchAll(); } // Get similar products based on attributes public function getSimilarProducts($productId, $limit = 10) { // Check pre-computed similarity $stmt = $this->pdo->prepare(" SELECT p.*, ps.similarity_score FROM product_similarity ps JOIN products p ON ps.product_id_2 = p.id WHERE ps.product_id_1 = ? ORDER BY ps.similarity_score DESC LIMIT ? "); $stmt->execute([$productId, $limit]); $similar = $stmt->fetchAll(); if (!empty($similar)) { return $similar; } // Calculate on the fly $stmt = $this->pdo->prepare(" SELECT p.*, COUNT(DISTINCT pa1.attribute_name) as match_count, COUNT(DISTINCT pa2.attribute_name) as total_attributes FROM products p JOIN product_attributes pa1 ON p.id = pa1.product_id JOIN product_attributes pa2 ON pa1.attribute_name = pa2.attribute_name AND pa1.attribute_value = pa2.attribute_value WHERE pa2.product_id = ? AND p.id != ? AND p.is_active = 1 GROUP BY p.id ORDER BY match_count DESC, p.sold_count DESC LIMIT ? "); $stmt->execute([$productId, $productId, $limit]); return $stmt->fetchAll(); } // Get frequently bought together products public function getFrequentlyBoughtTogether($productId, $limit = 5) { $stmt = $this->pdo->prepare(" SELECT p.*, COUNT(DISTINCT o.id) as order_count, AVG(oi2.price) as avg_price FROM products p JOIN order_items oi1 ON p.id = oi1.product_id JOIN order_items oi2 ON oi1.order_id = oi2.order_id JOIN orders o ON oi1.order_id = o.id WHERE oi2.product_id = ? AND p.id != ? AND p.is_active = 1 AND o.order_status = 'delivered' GROUP BY p.id ORDER BY order_count DESC, avg_price DESC LIMIT ? "); $stmt->execute([$productId, $productId, $limit]); return $stmt->fetchAll(); } // Get trending products (based on recent views/purchases) public function getTrendingProducts($limit = 20) { $stmt = $this->pdo->prepare(" SELECT p.*, COUNT(DISTINCT ua.id) as activity_count, COUNT(DISTINCT oi.id) as purchase_count FROM products p LEFT JOIN user_activities ua ON p.id = ua.product_id AND ua.created_at > DATE_SUB(NOW(), INTERVAL 7 DAY) LEFT JOIN order_items oi ON p.id = oi.product_id WHERE p.is_active = 1 GROUP BY p.id ORDER BY activity_count DESC, purchase_count DESC, p.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get popular products (all-time bestsellers) public function getPopularProducts($limit = 20) { $stmt = $this->pdo->prepare(" SELECT p.*, p.sold_count as popularity_score FROM products p WHERE p.is_active = 1 ORDER BY p.sold_count DESC, p.rating_avg DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get new products public function getNewProducts($limit = 20) { $stmt = $this->pdo->prepare(" SELECT p.* FROM products p WHERE p.is_active = 1 ORDER BY p.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Get category-based recommendations public function getCategoryRecommendations($userId, $categoryId, $limit = 20) { // Get user's preferred products in this category $stmt = $this->pdo->prepare(" SELECT p.*, ur.rating FROM products p JOIN user_ratings ur ON p.id = ur.product_id WHERE ur.user_id = ? AND p.category_id = ? ORDER BY ur.rating DESC LIMIT 10 "); $stmt->execute([$userId, $categoryId]); $preferred = $stmt->fetchAll(); if (empty($preferred)) { // No preferences, return popular in category $stmt = $this->pdo->prepare(" SELECT p.* FROM products p WHERE p.category_id = ? AND p.is_active = 1 ORDER BY p.sold_count DESC, p.rating_avg DESC LIMIT ? "); $stmt->execute([$categoryId, $limit]); return $stmt->fetchAll(); } // Build profile from preferred products and recommend similar $profile = $this->buildUserProfileFromProducts($preferred); return $this->getSimilarProductsByProfile($profile, $categoryId, $limit); } // Build profile from product list private function buildUserProfileFromProducts($products) { $profile = [ 'brands' => [], 'price_ranges' => [], 'attributes' => [] ]; foreach ($products as $product) { if ($product['brand_id']) { $profile['brands'][$product['brand_id']] = ($profile['brands'][$product['brand_id']] ?? 0) + 1; } $priceRange = $this->getPriceRange($product['price']); $profile['price_ranges'][$priceRange] = ($profile['price_ranges'][$priceRange] ?? 0) + 1; $attributes = $this->getProductAttributes($product['id']); foreach ($attributes as $attr) { $key = $attr['attribute_name'] . ':' . $attr['attribute_value']; $profile['attributes'][$key] = ($profile['attributes'][$key] ?? 0) + 1; } } // Normalize $total = count($products); foreach ($profile as &$values) { if (is_array($values)) { foreach ($values as &$value) { $value = $value / $total; } } } return $profile; } // Get products similar to profile private function getSimilarProductsByProfile($profile, $categoryId, $limit) { // This would require a more complex query with multiple joins // Simplified version: get products with matching attributes $stmt = $this->pdo->prepare(" SELECT p.*, COUNT(DISTINCT pa.attribute_name) as match_count FROM products p JOIN product_attributes pa ON p.id = pa.product_id WHERE p.category_id = ? AND p.is_active = 1 GROUP BY p.id ORDER BY match_count DESC, p.sold_count DESC LIMIT ? "); $stmt->execute([$categoryId, $limit]); return $stmt->fetchAll(); } // Get recommendation reason private function getRecommendationReason($product, $userProfile) { if (!empty($userProfile['categories']) && isset($userProfile['categories'][$product['category_id']])) { return 'Based on your interest in this category'; } if (!empty($userProfile['brands']) && isset($userProfile['brands'][$product['brand_id']])) { return 'From a brand you like'; } if ($this->calculateAttributesScore($product, $userProfile) > 0.5) { return 'Similar to products you viewed'; } if ($product['sold_count'] > 100) { return 'Popular product'; } return 'Recommended for you'; } // Cache recommendations private function cacheRecommendations($userId, $type, $recommendations) { // Clear old cache $stmt = $this->pdo->prepare(" DELETE FROM product_recommendations WHERE user_id = ? AND reason = ? "); $stmt->execute([$userId, $type]); // Insert new recommendations $stmt = $this->pdo->prepare(" INSERT INTO product_recommendations (user_id, product_id, score, reason, expires_at) VALUES (?, ?, ?, ?, DATE_ADD(NOW(), INTERVAL 1 DAY)) "); foreach ($recommendations as $rec) { $productId = is_array($rec) ? ($rec['product']['id'] ?? $rec['id']) : $rec['id']; $score = is_array($rec) ? ($rec['score'] ?? 0.5) : 0.5; $stmt->execute([ $userId, $productId, $score, $type ]); } } // Get cached recommendations private function getCachedRecommendations($userId, $type, $limit) { $stmt = $this->pdo->prepare(" SELECT pr.*, p.* FROM product_recommendations pr JOIN products p ON pr.product_id = p.id WHERE pr.user_id = ? AND pr.reason = ? AND pr.expires_at > NOW() ORDER BY pr.score DESC LIMIT ? "); $stmt->execute([$userId, $type, $limit]); return $stmt->fetchAll(); } // Pre-compute product similarity matrix (run via cron) public function computeProductSimilarity() { // Clear existing $this->pdo->exec("TRUNCATE TABLE product_similarity"); // Get all active products $products = $this->pdo->query(" SELECT id, category_id, brand_id FROM products WHERE is_active = 1 ")->fetchAll(); $insertStmt = $this->pdo->prepare(" INSERT INTO product_similarity (product_id_1, product_id_2, similarity_score, based_on) VALUES (?, ?, ?, ?) "); foreach ($products as $p1) { foreach ($products as $p2) { if ($p1['id'] >= $p2['id']) continue; $score = 0; $basedOn = []; // Category similarity if ($p1['category_id'] == $p2['category_id']) { $score += 0.4; $basedOn[] = 'category'; } // Brand similarity if ($p1['brand_id'] == $p2['brand_id']) { $score += 0.3; $basedOn[] = 'brand'; } // Attribute similarity $attrScore = $this->getAttributeSimilarity($p1['id'], $p2['id']); $score += $attrScore * 0.3; if ($score > 0.3) { $insertStmt->execute([ $p1['id'], $p2['id'], $score, implode(',', $basedOn) ]); } } } } // Calculate attribute similarity between two products private function getAttributeSimilarity($productId1, $productId2) { $stmt = $this->pdo->prepare(" SELECT COUNT(*) as match_count FROM product_attributes pa1 JOIN product_attributes pa2 ON pa1.attribute_name = pa2.attribute_name AND pa1.attribute_value = pa2.attribute_value WHERE pa1.product_id = ? AND pa2.product_id = ? "); $stmt->execute([$productId1, $productId2]); $matches = $stmt->fetchColumn(); $stmt = $this->pdo->prepare(" SELECT COUNT(*) as total FROM product_attributes WHERE product_id = ? "); $stmt->execute([$productId1]); $total1 = $stmt->fetchColumn(); $stmt->execute([$productId2]); $total2 = $stmt->fetchColumn(); $total = max($total1, $total2); return $total > 0 ? $matches / $total : 0; } } ?>

classes/Product.php

<?php class Product { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Get product by ID public function getById($id) { $stmt = $this->pdo->prepare(" SELECT p.*, c.name as category_name, c.slug as category_slug, b.name as brand_name, b.slug as brand_slug, (SELECT AVG(rating) FROM reviews WHERE product_id = p.id AND is_active = 1) as rating_avg, (SELECT COUNT(*) FROM reviews WHERE product_id = p.id AND is_active = 1) as review_count FROM products p LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN brands b ON p.brand_id = b.id WHERE p.id = ? AND p.is_active = 1 "); $stmt->execute([$id]); $product = $stmt->fetch(); if ($product) { // Get images $product['images'] = $this->getImages($id); // Get attributes $product['attributes'] = $this->getAttributes($id); // Increment view count $this->incrementViews($id); } return $product; } // Get product by slug public function getBySlug($slug) { $stmt = $this->pdo->prepare("SELECT id FROM products WHERE slug = ?"); $stmt->execute([$slug]); $product = $stmt->fetch(); if ($product) { return $this->getById($product['id']); } return null; } // Get product images public function getImages($productId) { $stmt = $this->pdo->prepare(" SELECT * FROM product_images WHERE product_id = ? ORDER BY is_primary DESC, sort_order ASC "); $stmt->execute([$productId]); return $stmt->fetchAll(); } // Get product attributes public function getAttributes($productId) { $stmt = $this->pdo->prepare(" SELECT * FROM product_attributes WHERE product_id = ? ORDER BY attribute_name "); $stmt->execute([$productId]); return $stmt->fetchAll(); } // Increment view count public function incrementViews($productId) { $stmt = $this->pdo->prepare("UPDATE products SET views_count = views_count + 1 WHERE id = ?"); $stmt->execute([$productId]); } // Search products public function search($query, $filters = [], $sort = 'relevance', $page = 1, $perPage = PRODUCTS_PER_PAGE) { $offset = ($page - 1) * $perPage; $sql = " SELECT p.*, c.name as category_name, b.name as brand_name, MATCH(p.name, p.description, p.short_description) AGAINST(:query) as relevance FROM products p LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN brands b ON p.brand_id = b.id WHERE p.is_active = 1 AND MATCH(p.name, p.description, p.short_description) AGAINST(:query) "; $params = ['query' => $query]; // Apply filters if (!empty($filters['category'])) { $sql .= " AND p.category_id = :category"; $params['category'] = $filters['category']; } if (!empty($filters['brand'])) { $sql .= " AND p.brand_id = :brand"; $params['brand'] = $filters['brand']; } if (!empty($filters['min_price'])) { $sql .= " AND p.price >= :min_price"; $params['min_price'] = $filters['min_price']; } if (!empty($filters['max_price'])) { $sql .= " AND p.price <= :max_price"; $params['max_price'] = $filters['max_price']; } if (!empty($filters['in_stock'])) { $sql .= " AND p.quantity > 0"; } // Apply sorting switch ($sort) { case 'price_low': $sql .= " ORDER BY p.price ASC"; break; case 'price_high': $sql .= " ORDER BY p.price DESC"; break; case 'newest': $sql .= " ORDER BY p.created_at DESC"; break; case 'bestselling': $sql .= " ORDER BY p.sold_count DESC"; break; case 'rating': $sql .= " ORDER BY p.rating_avg DESC"; break; default: $sql .= " ORDER BY relevance DESC, p.sold_count DESC"; } $sql .= " LIMIT :offset, :perPage"; $stmt = $this->pdo->prepare($sql); $stmt->bindParam(':query', $query); $stmt->bindParam(':offset', $offset, PDO::PARAM_INT); $stmt->bindParam(':perPage', $perPage, PDO::PARAM_INT); foreach ($params as $key => &$value) { $stmt->bindParam($key, $value); } $stmt->execute(); return $stmt->fetchAll(); } // Get products by category public function getByCategory($categoryId, $sort = 'newest', $page = 1, $perPage = PRODUCTS_PER_PAGE) { $offset = ($page - 1) * $perPage; $sql = " SELECT p.*, b.name as brand_name FROM products p LEFT JOIN brands b ON p.brand_id = b.id WHERE p.category_id = ? AND p.is_active = 1 "; switch ($sort) { case 'price_low': $sql .= " ORDER BY p.price ASC"; break; case 'price_high': $sql .= " ORDER BY p.price DESC"; break; case 'bestselling': $sql .= " ORDER BY p.sold_count DESC"; break; case 'rating': $sql .= " ORDER BY p.rating_avg DESC"; break; default: $sql .= " ORDER BY p.created_at DESC"; } $sql .= " LIMIT ?, ?"; $stmt = $this->pdo->prepare($sql); $stmt->execute([$categoryId, $offset, $perPage]); return $stmt->fetchAll(); } // Get products by brand public function getByBrand($brandId, $page = 1, $perPage = PRODUCTS_PER_PAGE) { $offset = ($page - 1) * $perPage; $stmt = $this->pdo->prepare(" SELECT p.*, c.name as category_name FROM products p LEFT JOIN categories c ON p.category_id = c.id WHERE p.brand_id = ? AND p.is_active = 1 ORDER BY p.created_at DESC LIMIT ?, ? "); $stmt->execute([$brandId, $offset, $perPage]); return $stmt->fetchAll(); } // Get featured products public function getFeatured($limit = 10) { $stmt = $this->pdo->prepare(" SELECT p.*, c.name as category_name, b.name as brand_name FROM products p LEFT JOIN categories c ON p.category_id = c.id LEFT JOIN brands b ON p.brand_id = b.id WHERE p.is_featured = 1 AND p.is_active = 1 ORDER BY p.created_at DESC LIMIT ? "); $stmt->execute([$limit]); return $stmt->fetchAll(); } // Add review public function addReview($productId, $userId, $rating, $title, $content) { // Check if already reviewed $stmt = $this->pdo->prepare("SELECT id FROM reviews WHERE product_id = ? AND user_id = ?"); $stmt->execute([$productId, $userId]); if ($stmt->fetch()) { return ['success' => false, 'message' => 'You have already reviewed this product']; } // Check if purchased $stmt = $this->pdo->prepare(" SELECT o.id FROM orders o JOIN order_items oi ON o.id = oi.order_id WHERE o.user_id = ? AND oi.product_id = ? AND o.order_status = 'delivered' "); $stmt->execute([$userId, $productId]); $isVerified = $stmt->fetch() ? true : false; // Insert review $stmt = $this->pdo->prepare(" INSERT INTO reviews (product_id, user_id, rating, title, content, is_verified_purchase) VALUES (?, ?, ?, ?, ?, ?) "); if ($stmt->execute([$productId, $userId, $rating, $title, $content, $isVerified])) { $this->updateProductRating($productId); // Log activity logUserActivity($userId, $productId, 'review', ['rating' => $rating]); return ['success' => true, 'review_id' => $this->pdo->lastInsertId()]; } return ['success' => false, 'message' => 'Failed to add review']; } // Update product rating private function updateProductRating($productId) { $stmt = $this->pdo->prepare(" UPDATE products SET rating_avg = (SELECT AVG(rating) FROM reviews WHERE product_id = ? AND is_active = 1), review_count = (SELECT COUNT(*) FROM reviews WHERE product_id = ? AND is_active = 1) WHERE id = ? "); $stmt->execute([$productId, $productId, $productId]); } // Get product reviews public function getReviews($productId, $page = 1, $perPage = 10) { $offset = ($page - 1) * $perPage; $stmt = $this->pdo->prepare(" SELECT r.*, u.username, u.full_name, u.profile_pic FROM reviews r JOIN users u ON r.user_id = u.id WHERE r.product_id = ? AND r.is_active = 1 ORDER BY r.created_at DESC LIMIT ?, ? "); $stmt->execute([$productId, $offset, $perPage]); return $stmt->fetchAll(); } // Mark review as helpful public function markReviewHelpful($reviewId, $userId) { $stmt = $this->pdo->prepare(" INSERT IGNORE INTO review_helpful (review_id, user_id) VALUES (?, ?) "); if ($stmt->execute([$reviewId, $userId])) { // Update helpful count $stmt = $this->pdo->prepare(" UPDATE reviews SET helpful_count = helpful_count + 1 WHERE id = ? "); $stmt->execute([$reviewId]); return true; } return false; } } ?>

classes/Cart.php

<?php class Cart { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Get or create cart public function getCart($userId = null, $sessionId = null) { if ($userId) { // Get user's cart $stmt = $this->pdo->prepare("SELECT id FROM carts WHERE user_id = ?"); $stmt->execute([$userId]); $cart = $stmt->fetch(); if ($cart) { return $this->getCartItems($cart['id']); } else { // Create new cart $stmt = $this->pdo->prepare("INSERT INTO carts (user_id) VALUES (?)"); $stmt->execute([$userId]); $cartId = $this->pdo->lastInsertId(); return ['id' => $cartId, 'items' => [], 'total' => 0]; } } elseif ($sessionId) { // Get session cart $stmt = $this->pdo->prepare("SELECT id FROM carts WHERE session_id = ?"); $stmt->execute([$sessionId]); $cart = $stmt->fetch(); if ($cart) { return $this->getCartItems($cart['id']); } else { // Create new cart $stmt = $this->pdo->prepare("INSERT INTO carts (session_id) VALUES (?)"); $stmt->execute([$sessionId]); $cartId = $this->pdo->lastInsertId(); return ['id' => $cartId, 'items' => [], 'total' => 0]; } } return null; } // Get cart items private function getCartItems($cartId) { $stmt = $this->pdo->prepare(" SELECT ci.*, p.name, p.slug, p.price as current_price, pi.image_url as image FROM cart_items ci JOIN products p ON ci.product_id = p.id LEFT JOIN product_images pi ON p.id = pi.product_id AND pi.is_primary = 1 WHERE ci.cart_id = ? "); $stmt->execute([$cartId]); $items = $stmt->fetchAll(); // Calculate totals $subtotal = 0; foreach ($items as &$item) { $item['total'] = $item['price'] * $item['quantity']; $subtotal += $item['total']; } $tax = $subtotal * (DEFAULT_TAX_RATE / 100); $shipping = $subtotal >= FREE_SHIPPING_THRESHOLD ? 0 : DEFAULT_SHIPPING_COST; $total = $subtotal + $tax + $shipping; return [ 'id' => $cartId, 'items' => $items, 'subtotal' => $subtotal, 'tax' => $tax, 'shipping' => $shipping, 'total' => $total, 'item_count' => count($items), 'quantity_total' => array_sum(array_column($items, 'quantity')) ]; } // Add item to cart public function addItem($cartId, $productId, $quantity = 1) { // Check if product exists and has stock $stmt = $this->pdo->prepare("SELECT * FROM products WHERE id = ? AND is_active = 1"); $stmt->execute([$productId]); $product = $stmt->fetch(); if (!$product) { return ['success' => false, 'message' => 'Product not found']; } if ($product['quantity'] < $quantity) { return ['success' => false, 'message' => 'Insufficient stock']; } // Check if already in cart $stmt = $this->pdo->prepare("SELECT * FROM cart_items WHERE cart_id = ? AND product_id = ?"); $stmt->execute([$cartId, $productId]); $existing = $stmt->fetch(); if ($existing) { // Update quantity $newQuantity = $existing['quantity'] + $quantity; if ($product['quantity'] < $newQuantity) { return ['success' => false, 'message' => 'Insufficient stock']; } $stmt = $this->pdo->prepare(" UPDATE cart_items SET quantity = ?, price = ? WHERE id = ? "); $stmt->execute([$newQuantity, $product['price'], $existing['id']]); } else { // Add new item $stmt = $this->pdo->prepare(" INSERT INTO cart_items (cart_id, product_id, quantity, price) VALUES (?, ?, ?, ?) "); $stmt->execute([$cartId, $productId, $quantity, $product['price']]); } return ['success' => true, 'cart' => $this->getCartItems($cartId)]; } // Update item quantity public function updateQuantity($cartId, $productId, $quantity) { if ($quantity <= 0) { return $this->removeItem($cartId, $productId); } // Check stock $stmt = $this->pdo->prepare("SELECT quantity FROM products WHERE id = ?"); $stmt->execute([$productId]); $stock = $stmt->fetchColumn(); if ($stock < $quantity) { return ['success' => false, 'message' => 'Insufficient stock']; } $stmt = $this->pdo->prepare(" UPDATE cart_items SET quantity = ? WHERE cart_id = ? AND product_id = ? "); $stmt->execute([$quantity, $cartId, $productId]); return ['success' => true, 'cart' => $this->getCartItems($cartId)]; } // Remove item from cart public function removeItem($cartId, $productId) { $stmt = $this->pdo->prepare("DELETE FROM cart_items WHERE cart_id = ? AND product_id = ?"); $stmt->execute([$cartId, $productId]); return ['success' => true, 'cart' => $this->getCartItems($cartId)]; } // Clear cart public function clearCart($cartId) { $stmt = $this->pdo->prepare("DELETE FROM cart_items WHERE cart_id = ?"); $stmt->execute([$cartId]); return ['success' => true]; } // Merge session cart with user cart after login public function mergeCarts($userId, $sessionId) { // Get session cart $stmt = $this->pdo->prepare("SELECT id FROM carts WHERE session_id = ?"); $stmt->execute([$sessionId]); $sessionCart = $stmt->fetch(); if (!$sessionCart) { return; } // Get user cart $stmt = $this->pdo->prepare("SELECT id FROM carts WHERE user_id = ?"); $stmt->execute([$userId]); $userCart = $stmt->fetch(); if (!$userCart) { // Assign session cart to user $stmt = $this->pdo->prepare("UPDATE carts SET user_id = ?, session_id = NULL WHERE id = ?"); $stmt->execute([$userId, $sessionCart['id']]); } else { // Move items from session cart to user cart $stmt = $this->pdo->prepare(" SELECT * FROM cart_items WHERE cart_id = ? "); $stmt->execute([$sessionCart['id']]); $items = $stmt->fetchAll(); foreach ($items as $item) { // Check if already in user cart $stmt = $this->pdo->prepare(" SELECT id FROM cart_items WHERE cart_id = ? AND product_id = ? "); $stmt->execute([$userCart['id'], $item['product_id']]); if ($stmt->fetch()) { // Update quantity $stmt = $this->pdo->prepare(" UPDATE cart_items SET quantity = quantity + ? WHERE cart_id = ? AND product_id = ? "); $stmt->execute([$item['quantity'], $userCart['id'], $item['product_id']]); } else { // Insert new $stmt = $this->pdo->prepare(" INSERT INTO cart_items (cart_id, product_id, quantity, price) VALUES (?, ?, ?, ?) "); $stmt->execute([$userCart['id'], $item['product_id'], $item['quantity'], $item['price']]); } } // Delete session cart $stmt = $this->pdo->prepare("DELETE FROM carts WHERE id = ?"); $stmt->execute([$sessionCart['id']]); } } } ?>

classes/Order.php

<?php class Order { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Create order from cart public function createFromCart($userId, $cart, $addressId, $paymentMethod) { try { $this->pdo->beginTransaction(); // Get address $stmt = $this->pdo->prepare("SELECT * FROM addresses WHERE id = ? AND user_id = ?"); $stmt->execute([$addressId, $userId]); $address = $stmt->fetch(); if (!$address) { throw new Exception('Invalid shipping address'); } // Generate order number $orderNumber = generateOrderNumber(); // Insert order $stmt = $this->pdo->prepare(" INSERT INTO orders ( order_number, user_id, shipping_address_id, billing_address_id, subtotal, shipping_cost, tax_amount, total_amount, payment_method ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) "); $stmt->execute([ $orderNumber, $userId, $addressId, $addressId, // Same for billing in this example $cart['subtotal'], $cart['shipping'], $cart['tax'], $cart['total'], $paymentMethod ]); $orderId = $this->pdo->lastInsertId(); // Insert order items and update stock foreach ($cart['items'] as $item) { // Insert order item $stmt = $this->pdo->prepare(" INSERT INTO order_items ( order_id, product_id, seller_id, product_name, product_sku, quantity, price, total ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) "); // Get seller ID from product $sellerStmt = $this->pdo->prepare("SELECT seller_id, name, sku FROM products WHERE id = ?"); $sellerStmt->execute([$item['product_id']]); $product = $sellerStmt->fetch(); $stmt->execute([ $orderId, $item['product_id'], $product['seller_id'], $product['name'], $product['sku'], $item['quantity'], $item['price'], $item['price'] * $item['quantity'] ]); // Update product stock $updateStmt = $this->pdo->prepare(" UPDATE products SET quantity = quantity - ?, sold_count = sold_count + ? WHERE id = ? "); $updateStmt->execute([$item['quantity'], $item['quantity'], $item['product_id']]); // Log purchase activity for recommendations logUserActivity($userId, $item['product_id'], 'purchase', [ 'order_id' => $orderId, 'quantity' => $item['quantity'] ]); } // Clear cart $cartStmt = $this->pdo->prepare("DELETE FROM cart_items WHERE cart_id = ?"); $cartStmt->execute([$cart['id']]); $this->pdo->commit(); return ['success' => true, 'order_id' => $orderId, 'order_number' => $orderNumber]; } catch (Exception $e) { $this->pdo->rollBack(); return ['success' => false, 'message' => $e->getMessage()]; } } // Get user orders public function getUserOrders($userId, $page = 1, $perPage = 20) { $offset = ($page - 1) * $perPage; $stmt = $this->pdo->prepare(" SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC LIMIT ?, ? "); $stmt->execute([$userId, $offset, $perPage]); $orders = $stmt->fetchAll(); foreach ($orders as &$order) { $order['items'] = $this->getOrderItems($order['id']); } return $orders; } // Get order by ID public function getOrder($orderId, $userId = null) { $sql = "SELECT * FROM orders WHERE id = ?"; $params = [$orderId]; if ($userId) { $sql .= " AND user_id = ?"; $params[] = $userId; } $stmt = $this->pdo->prepare($sql); $stmt->execute($params); $order = $stmt->fetch(); if ($order) { $order['items'] = $this->getOrderItems($orderId); $order['shipping_address'] = $this->getAddress($order['shipping_address_id']); $order['billing_address'] = $this->getAddress($order['billing_address_id']); } return $order; } // Get order by number public function getByOrderNumber($orderNumber) { $stmt = $this->pdo->prepare("SELECT id FROM orders WHERE order_number = ?"); $stmt->execute([$orderNumber]); $order = $stmt->fetch(); if ($order) { return $this->getOrder($order['id']); } return null; } // Get order items private function getOrderItems($orderId) { $stmt = $this->pdo->prepare(" SELECT oi.*, p.slug, pi.image_url as image FROM order_items oi LEFT JOIN products p ON oi.product_id = p.id LEFT JOIN product_images pi ON p.id = pi.product_id AND pi.is_primary = 1 WHERE oi.order_id = ? "); $stmt->execute([$orderId]); return $stmt->fetchAll(); } // Get address private function getAddress($addressId) { $stmt = $this->pdo->prepare("SELECT * FROM addresses WHERE id = ?"); $stmt->execute([$addressId]); return $stmt->fetch(); } // Update order status public function updateStatus($orderId, $status, $userId = null, $isSeller = false) { $order = $this->getOrder($orderId); if (!$order) { return ['success' => false, 'message' => 'Order not found']; } // Check permissions if ($userId && !$isSeller && $order['user_id'] != $userId) { return ['success' => false, 'message' => 'Unauthorized']; } $allowedStatuses = ['processing', 'shipped', 'delivered', 'cancelled']; if (!in_array($status, $allowedStatuses)) { return ['success' => false, 'message' => 'Invalid status']; } $field = $status . '_at'; $stmt = $this->pdo->prepare(" UPDATE orders SET order_status = ?, $field = NOW() WHERE id = ? "); if ($stmt->execute([$status, $orderId])) { return ['success' => true]; } return ['success' => false, 'message' => 'Failed to update status']; } // Cancel order public function cancelOrder($orderId, $userId) { $order = $this->getOrder($orderId, $userId); if (!$order) { return ['success' => false, 'message' => 'Order not found']; } if ($order['order_status'] != 'pending') { return ['success' => false, 'message' => 'Order cannot be cancelled']; } // Restore stock foreach ($order['items'] as $item) { $stmt = $this->pdo->prepare(" UPDATE products SET quantity = quantity + ?, sold_count = sold_count - ? WHERE id = ? "); $stmt->execute([$item['quantity'], $item['quantity'], $item['product_id']]); } // Update order status $stmt = $this->pdo->prepare(" UPDATE orders SET order_status = 'cancelled', cancelled_at = NOW() WHERE id = ? "); if ($stmt->execute([$orderId])) { return ['success' => true]; } return ['success' => false, 'message' => 'Failed to cancel order']; } } ?>

classes/Address.php

<?php class Address { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Get user addresses public function getUserAddresses($userId) { $stmt = $this->pdo->prepare(" SELECT * FROM addresses WHERE user_id = ? ORDER BY is_default DESC, created_at DESC "); $stmt->execute([$userId]); return $stmt->fetchAll(); } // Get address by ID public function getById($id, $userId = null) { $sql = "SELECT * FROM addresses WHERE id = ?"; $params = [$id]; if ($userId) { $sql .= " AND user_id = ?"; $params[] = $userId; } $stmt = $this->pdo->prepare($sql); $stmt->execute($params); return $stmt->fetch(); } // Add address public function add($userId, $data) { // If this is first address or set as default, update others if ($data['is_default'] ?? false) { $this->clearDefault($userId); } $stmt = $this->pdo->prepare(" INSERT INTO addresses ( user_id, address_type, recipient_name, address_line1, address_line2, city, state, postal_code, country, phone, is_default ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) "); $success = $stmt->execute([ $userId, $data['address_type'] ?? 'shipping', $data['recipient_name'], $data['address_line1'], $data['address_line2'] ?? null, $data['city'], $data['state'] ?? null, $data['postal_code'], $data['country'] ?? 'USA', $data['phone'] ?? null, $data['is_default'] ?? false ]); if ($success) { return ['success' => true, 'address_id' => $this->pdo->lastInsertId()]; } return ['success' => false, 'message' => 'Failed to add address']; } // Update address public function update($id, $userId, $data) { $address = $this->getById($id, $userId); if (!$address) { return ['success' => false, 'message' => 'Address not found']; } // Handle default if (($data['is_default'] ?? false) && !$address['is_default']) { $this->clearDefault($userId); } $stmt = $this->pdo->prepare(" UPDATE addresses SET address_type = ?, recipient_name = ?, address_line1 = ?, address_line2 = ?, city = ?, state = ?, postal_code = ?, country = ?, phone = ?, is_default = ? WHERE id = ? AND user_id = ? "); $success = $stmt->execute([ $data['address_type'] ?? $address['address_type'], $data['recipient_name'] ?? $address['recipient_name'], $data['address_line1'] ?? $address['address_line1'], $data['address_line2'] ?? $address['address_line2'], $data['city'] ?? $address['city'], $data['state'] ?? $address['state'], $data['postal_code'] ?? $address['postal_code'], $data['country'] ?? $address['country'], $data['phone'] ?? $address['phone'], $data['is_default'] ?? $address['is_default'], $id, $userId ]); return ['success' => $success]; } // Delete address public function delete($id, $userId) { $address = $this->getById($id, $userId); if (!$address) { return ['success' => false, 'message' => 'Address not found']; } if ($address['is_default']) { return ['success' => false, 'message' => 'Cannot delete default address']; } $stmt = $this->pdo->prepare("DELETE FROM addresses WHERE id = ? AND user_id = ?"); $success = $stmt->execute([$id, $userId]); return ['success' => $success]; } // Clear default address for user private function clearDefault($userId) { $stmt = $this->pdo->prepare("UPDATE addresses SET is_default = 0 WHERE user_id = ?"); $stmt->execute([$userId]); } // Get default address public function getDefault($userId, $type = 'shipping') { $stmt = $this->pdo->prepare(" SELECT * FROM addresses WHERE user_id = ? AND address_type = ? AND is_default = 1 LIMIT 1 "); $stmt->execute([$userId, $type]); return $stmt->fetch(); } } ?>

API ENDPOINTS

api/products.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? ''; switch ($action) { case 'get': $id = $_GET['id'] ?? 0; $slug = $_GET['slug'] ?? ''; $product = new Product($pdo); if ($id) { $data = $product->getById($id); } elseif ($slug) { $data = $product->getBySlug($slug); } else { jsonResponse(['success' => false, 'message' => 'Product ID or slug required'], 400); } if ($data) { jsonResponse(['success' => true, 'product' => $data]); } else { jsonResponse(['success' => false, 'message' => 'Product not found'], 404); } break; case 'search': $query = $_GET['q'] ?? ''; $page = $_GET['page'] ?? 1; $sort = $_GET['sort'] ?? 'relevance'; $filters = [ 'category' => $_GET['category'] ?? null, 'brand' => $_GET['brand'] ?? null, 'min_price' => $_GET['min_price'] ?? null, 'max_price' => $_GET['max_price'] ?? null, 'in_stock' => $_GET['in_stock'] ?? false ]; $product = new Product($pdo); $results = $product->search($query, $filters, $sort, $page); jsonResponse(['success' => true, 'products' => $results]); break; case 'category': $categoryId = $_GET['id'] ?? 0; $page = $_GET['page'] ?? 1; $sort = $_GET['sort'] ?? 'newest'; $product = new Product($pdo); $products = $product->getByCategory($categoryId, $sort, $page); jsonResponse(['success' => true, 'products' => $products]); break; case 'featured': $limit = $_GET['limit'] ?? 10; $product = new Product($pdo); $products = $product->getFeatured($limit); jsonResponse(['success' => true, 'products' => $products]); break; case 'reviews': $productId = $_GET['id'] ?? 0; $page = $_GET['page'] ?? 1; $product = new Product($pdo); $reviews = $product->getReviews($productId, $page); jsonResponse(['success' => true, 'reviews' => $reviews]); break; case 'add_review': if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $product = new Product($pdo); $result = $product->addReview( $data['product_id'], getCurrentUserId(), $data['rating'], $data['title'], $data['content'] ); jsonResponse($result); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/recommendations.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? 'personalized'; $userId = getCurrentUserId() ?? 0; $recommender = new ProductRecommender($pdo); switch ($action) { case 'personalized': if (!$userId) { // Not logged in, show popular products $products = $recommender->getPopularProducts($_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $products]); } else { $recommendations = $recommender->getRecommendations($userId, 'personalized', $_GET['limit'] ?? RECOMMENDATION_LIMIT); jsonResponse(['success' => true, 'recommendations' => $recommendations]); } break; case 'similar': $productId = $_GET['product_id'] ?? 0; if (!$productId) { jsonResponse(['success' => false, 'message' => 'Product ID required'], 400); } $recommendations = $recommender->getSimilarProducts($productId, $_GET['limit'] ?? 10); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'frequently_bought': $productId = $_GET['product_id'] ?? 0; if (!$productId) { jsonResponse(['success' => false, 'message' => 'Product ID required'], 400); } $recommendations = $recommender->getFrequentlyBoughtTogether($productId, $_GET['limit'] ?? 5); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'trending': $recommendations = $recommender->getTrendingProducts($_GET['limit'] ?? 20); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'popular': $recommendations = $recommender->getPopularProducts($_GET['limit'] ?? 20); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; case 'new': $recommendations = $recommender->getNewProducts($_GET['limit'] ?? 20); jsonResponse(['success' => true, 'recommendations' => $recommendations]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/cart.php

<?php require_once '../config.php'; $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); $sessionId = session_id(); $cart = new Cart($pdo); // Merge cart if user just logged in if ($userId && isset($_SESSION['merged']) && !$_SESSION['merged']) { $cart->mergeCarts($userId, $sessionId); $_SESSION['merged'] = true; } switch ($action) { case 'get': $cartData = $cart->getCart($userId, $sessionId); jsonResponse(['success' => true, 'cart' => $cartData]); break; case 'add': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $cartData = $cart->getCart($userId, $sessionId); $result = $cart->addItem( $cartData['id'], $data['product_id'], $data['quantity'] ?? 1 ); if ($result['success'] && $userId) { logUserActivity($userId, $data['product_id'], 'cart_add', ['quantity' => $data['quantity'] ?? 1]); } jsonResponse($result); break; case 'update': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $cartData = $cart->getCart($userId, $sessionId); $result = $cart->updateQuantity( $cartData['id'], $data['product_id'], $data['quantity'] ); jsonResponse($result); break; case 'remove': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $cartData = $cart->getCart($userId, $sessionId); $result = $cart->removeItem( $cartData['id'], $data['product_id'] ); jsonResponse($result); break; case 'clear': $cartData = $cart->getCart($userId, $sessionId); $result = $cart->clearCart($cartData['id']); jsonResponse($result); break; case 'count': $cartData = $cart->getCart($userId, $sessionId); jsonResponse(['success' => true, 'count' => $cartData['quantity_total'] ?? 0]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/orders.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); $order = new Order($pdo); switch ($action) { case 'create': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); // Get cart $cartHandler = new Cart($pdo); $cart = $cartHandler->getCart($userId, null); if (empty($cart['items'])) { jsonResponse(['success' => false, 'message' => 'Cart is empty']); } $result = $order->createFromCart( $userId, $cart, $data['address_id'], $data['payment_method'] ); jsonResponse($result); break; case 'list': $page = $_GET['page'] ?? 1; $orders = $order->getUserOrders($userId, $page); jsonResponse(['success' => true, 'orders' => $orders]); break; case 'get': $id = $_GET['id'] ?? 0; $orderData = $order->getOrder($id, $userId); if ($orderData) { jsonResponse(['success' => true, 'order' => $orderData]); } else { jsonResponse(['success' => false, 'message' => 'Order not found'], 404); } break; case 'track': $orderNumber = $_GET['number'] ?? ''; $orderData = $order->getByOrderNumber($orderNumber); if ($orderData && $orderData['user_id'] == $userId) { jsonResponse(['success' => true, 'order' => $orderData]); } else { jsonResponse(['success' => false, 'message' => 'Order not found'], 404); } break; case 'cancel': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $result = $order->cancelOrder($data['order_id'], $userId); jsonResponse($result); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

api/addresses.php

<?php require_once '../config.php'; if (!isLoggedIn()) { jsonResponse(['success' => false, 'message' => 'Not authenticated'], 401); } $action = $_GET['action'] ?? ''; $userId = getCurrentUserId(); $address = new Address($pdo); switch ($action) { case 'list': $addresses = $address->getUserAddresses($userId); jsonResponse(['success' => true, 'addresses' => $addresses]); break; case 'get': $id = $_GET['id'] ?? 0; $addressData = $address->getById($id, $userId); if ($addressData) { jsonResponse(['success' => true, 'address' => $addressData]); } else { jsonResponse(['success' => false, 'message' => 'Address not found'], 404); } break; case 'add': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $result = $address->add($userId, $data); jsonResponse($result); break; case 'update': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $result = $address->update($data['id'], $userId, $data); jsonResponse($result); break; case 'delete': if ($_SERVER['REQUEST_METHOD'] !== 'POST') { jsonResponse(['success' => false, 'message' => 'Method not allowed'], 405); } $data = json_decode(file_get_contents('php://input'), true); $result = $address->delete($data['id'], $userId); jsonResponse($result); break; case 'default': $type = $_GET['type'] ?? 'shipping'; $addressData = $address->getDefault($userId, $type); jsonResponse(['success' => true, 'address' => $addressData]); break; default: jsonResponse(['success' => false, 'message' => 'Invalid action'], 400); } ?>

FRONTEND IMPLEMENTATION (Simplified)

index.html (Main Store Front)

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>ShopEase - Your Online Store</title> <!-- Tailwind CSS --> <script src="https://cdn.tailwindcss.com"></script> <!-- Font Awesome --> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"> <style> /* Custom animations */ @keyframes slideIn { from { transform: translateY(20px); opacity: 0; } to { transform: translateY(0); opacity: 1; } } .product-card { animation: slideIn 0.3s ease-out; transition: transform 0.2s, box-shadow 0.2s; } .product-card:hover { transform: translateY(-5px); box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1); } /* Loading spinner */ .loading-spinner { border: 3px solid #f3f3f3; border-top: 3px solid #3b82f6; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } /* Cart badge */ .cart-badge { position: absolute; top: -8px; right: -8px; background: #ef4444; color: white; border-radius: 50%; min-width: 20px; height: 20px; font-size: 12px; display: flex; align-items: center; justify-content: center; } </style> </head> <body class="bg-gray-50"> <!-- Navigation --> <nav class="bg-white shadow-lg sticky top-0 z-40"> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-16"> <!-- Logo --> <div class="flex items-center"> <a href="index.html" class="text-2xl font-bold text-blue-600"> <i class="fas fa-store mr-2"></i>ShopEase </a> </div> <!-- Search Bar --> <div class="flex-1 max-w-lg mx-8 hidden md:block"> <div class="relative"> <input type="text" id="searchInput" placeholder="Search products..." class="w-full px-4 py-2 pl-10 pr-4 rounded-lg border border-gray-300 focus:outline-none focus:ring-2 focus:ring-blue-500" onkeypress="if(event.key === 'Enter') searchProducts()"> <i class="fas fa-search absolute left-3 top-3 text-gray-400"></i> </div> </div> <!-- Right Navigation --> <div class="flex items-center space-x-4"> <div class="relative"> <a href="cart.html" class="text-gray-700 hover:text-blue-600"> <i class="fas fa-shopping-cart text-xl"></i> <span id="cartCount" class="cart-badge hidden">0</span> </a> </div> <div class="relative" id="userMenu"> <!-- Will be populated by JavaScript --> </div> </div> </div> </div> </nav> <!-- Main Content --> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8"> <!-- Hero Banner --> <div class="bg-gradient-to-r from-blue-500 to-purple-600 rounded-lg p-8 mb-8 text-white"> <h1 class="text-4xl font-bold mb-4">Welcome to ShopEase</h1> <p class="text-xl mb-6">Discover amazing products at great prices</p> <a href="#featured" class="bg-white text-blue-600 px-6 py-2 rounded-lg font-semibold hover:bg-gray-100"> Shop Now </a> </div> <!-- Categories --> <div class="mb-8"> <h2 class="text-2xl font-bold mb-4">Shop by Category</h2> <div id="categories" class="grid grid-cols-2 md:grid-cols-4 lg:grid-cols-8 gap-4"> <!-- Loaded via JavaScript --> </div> </div> <!-- Personalized Recommendations --> <div id="recommendationsSection" class="mb-8"> <h2 class="text-2xl font-bold mb-4">Recommended for You</h2> <div id="recommendations" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <!-- Loaded via JavaScript --> </div> </div> <!-- Trending Products --> <div class="mb-8"> <h2 class="text-2xl font-bold mb-4">Trending Now</h2> <div id="trending" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <!-- Loaded via JavaScript --> </div> </div> <!-- Featured Products --> <div id="featured" class="mb-8"> <h2 class="text-2xl font-bold mb-4">Featured Products</h2> <div id="featuredProducts" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6"> <!-- Loaded via JavaScript --> </div> </div> </div> <!-- Quick View Modal --> <div id="quickViewModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg max-w-4xl w-full mx-4 max-h-[90vh] overflow-y-auto"> <div class="p-6"> <div class="flex justify-between items-center mb-4"> <h3 class="text-2xl font-bold" id="modalTitle"></h3> <button onclick="closeModal()" class="text-gray-500 hover:text-gray-700"> <i class="fas fa-times text-2xl"></i> </button> </div> <div id="modalContent"></div> </div> </div> </div> <!-- Login Modal --> <div id="loginModal" class="fixed inset-0 bg-black bg-opacity-50 z-50 hidden flex items-center justify-center"> <div class="bg-white rounded-lg max-w-md w-full mx-4 p-6"> <h3 class="text-2xl font-bold mb-4">Login</h3> <form onsubmit="login(event)"> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Email</label> <input type="email" id="loginEmail" class="w-full px-3 py-2 border rounded-lg" required> </div> <div class="mb-4"> <label class="block text-sm font-medium text-gray-700 mb-2">Password</label> <input type="password" id="loginPassword" class="w-full px-3 py-2 border rounded-lg" required> </div> <button type="submit" class="w-full bg-blue-600 text-white py-2 rounded-lg hover:bg-blue-700"> Login </button> </form> <p class="text-center mt-4"> Don't have an account? <a href="#" onclick="showRegister()" class="text-blue-600">Register</a> </p> </div> </div> <script> // State let currentUser = null; let cartCount = 0; // Initialize document.addEventListener('DOMContentLoaded', function() { checkAuth(); loadCategories(); loadRecommendations(); loadTrending(); loadFeatured(); updateCartCount(); }); // Check authentication function checkAuth() { fetch('api/auth.php?action=check') .then(response => response.json()) .then(data => { if (data.logged_in) { currentUser = data.user; updateUserMenu(); } else { updateGuestMenu(); } }); } // Update user menu function updateUserMenu() { const menu = document.getElementById('userMenu'); menu.innerHTML = ` <div class="relative"> <button onclick="toggleDropdown()" class="flex items-center space-x-2"> <img src="${currentUser.profile_pic || 'assets/images/default-avatar.png'}" class="w-8 h-8 rounded-full object-cover"> <span>${currentUser.full_name || currentUser.username}</span> <i class="fas fa-chevron-down text-sm"></i> </button> <div id="userDropdown" class="hidden absolute right-0 mt-2 w-48 bg-white rounded-lg shadow-lg py-1 z-50"> <a href="profile.html" class="block px-4 py-2 hover:bg-gray-100">Profile</a> <a href="orders.html" class="block px-4 py-2 hover:bg-gray-100">Orders</a> <a href="wishlist.html" class="block px-4 py-2 hover:bg-gray-100">Wishlist</a> <hr class="my-1"> <a href="#" onclick="logout()" class="block px-4 py-2 text-red-600 hover:bg-gray-100">Logout</a> </div> </div> `; } // Update guest menu function updateGuestMenu() { const menu = document.getElementById('userMenu'); menu.innerHTML = ` <button onclick="showLoginModal()" class="text-gray-700 hover:text-blue-600"> Login </button> <button onclick="showRegisterModal()" class="bg-blue-600 text-white px-4 py-2 rounded-lg hover:bg-blue-700"> Register </button> `; } // Load categories function loadCategories() { fetch('api/categories.php') .then(response => response.json()) .then(data => { if (data.success) { const container = document.getElementById('categories'); container.innerHTML = data.categories.map(cat => ` <a href="category.html?id=${cat.id}" class="bg-white p-4 rounded-lg text-center hover:shadow-md transition"> <i class="fas fa-${getCategoryIcon(cat.name)} text-3xl text-blue-600 mb-2"></i> <p class="font-medium">${cat.name}</p> </a> `).join(''); } }); } // Get category icon function getCategoryIcon(name) { const icons = { 'Electronics': 'laptop', 'Clothing': 'tshirt', 'Books': 'book', 'Home & Garden': 'home', 'Sports': 'futbol', 'Toys': 'gamepad', 'Health': 'heart', 'Automotive': 'car' }; return icons[name] || 'tag'; } // Load recommendations function loadRecommendations() { fetch('api/recommendations.php?action=personalized&limit=8') .then(response => response.json()) .then(data => { if (data.success) { const container = document.getElementById('recommendations'); if (data.recommendations.length > 0) { container.innerHTML = data.recommendations.map(renderProduct).join(''); } else { document.getElementById('recommendationsSection').style.display = 'none'; } } }); } // Load trending products function loadTrending() { fetch('api/recommendations.php?action=trending&limit=8') .then(response => response.json()) .then(data => { if (data.success) { const container = document.getElementById('trending'); container.innerHTML = data.recommendations.map(renderProduct).join(''); } }); } // Load featured products function loadFeatured() { fetch('api/products.php?action=featured&limit=8') .then(response => response.json()) .then(data => { if (data.success) { const container = document.getElementById('featuredProducts'); container.innerHTML = data.products.map(renderProduct).join(''); } }); } // Render product card function renderProduct(product) { const discount = product.compare_at_price > product.price ? Math.round(((product.compare_at_price - product.price) / product.compare_at_price) * 100) : 0; return ` <div class="product-card bg-white rounded-lg shadow overflow-hidden"> <a href="product.html?id=${product.id}" class="block"> <div class="relative pb-[100%] bg-gray-200"> <img src="${product.image || 'assets/images/no-image.png'}" alt="${product.name}" class="absolute inset-0 w-full h-full object-cover"> ${discount > 0 ? ` <span class="absolute top-2 left-2 bg-red-500 text-white text-xs px-2 py-1 rounded"> -${discount}% </span> ` : ''} </div> <div class="p-4"> <h3 class="font-semibold text-lg mb-2 truncate">${product.name}</h3> <div class="flex items-center mb-2"> ${getRatingStars(product.rating_avg)} <span class="text-sm text-gray-600 ml-2">(${product.review_count || 0})</span> </div> <div class="flex items-center justify-between"> <div> <span class="text-xl font-bold text-blue-600">$${product.price}</span> ${product.compare_at_price > product.price ? ` <span class="text-sm text-gray-500 line-through ml-2">$${product.compare_at_price}</span> ` : ''} </div> <button onclick="addToCart(${product.id}, event)" class="text-blue-600 hover:text-blue-800"> <i class="fas fa-shopping-cart"></i> </button> </div> </div> </a> </div> `; } // Get rating stars function getRatingStars(rating) { const stars = []; for (let i = 1; i <= 5; i++) { if (i <= rating) { stars.push('<i class="fas fa-star text-yellow-400"></i>'); } else if (i - 0.5 <= rating) { stars.push('<i class="fas fa-star-half-alt text-yellow-400"></i>'); } else { stars.push('<i class="far fa-star text-yellow-400"></i>'); } } return stars.join(''); } // Add to cart function addToCart(productId, event) { event.preventDefault(); event.stopPropagation(); fetch('api/cart.php?action=add', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ product_id: productId, quantity: 1 }) }) .then(response => response.json()) .then(data => { if (data.success) { updateCartCount(); showNotification('Product added to cart'); } }); } // Update cart count function updateCartCount() { fetch('api/cart.php?action=count') .then(response => response.json()) .then(data => { if (data.success && data.count > 0) { const badge = document.getElementById('cartCount'); badge.textContent = data.count; badge.classList.remove('hidden'); } }); } // Search products function searchProducts() { const query = document.getElementById('searchInput').value; if (query) { window.location.href = `search.html?q=${encodeURIComponent(query)}`; } } // Login function login(event) { event.preventDefault(); const email = document.getElementById('loginEmail').value; const password = document.getElementById('loginPassword').value; fetch('api/auth.php?action=login', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ username: email, password }) }) .then(response => response.json()) .then(data => { if (data.success) { closeModal(); checkAuth(); showNotification('Login successful'); } else { alert('Login failed: ' + data.message); } }); } // Logout function logout() { fetch('api/auth.php?action=logout') .then(() => { currentUser = null; updateGuestMenu(); showNotification('Logged out'); }); } // Show notification function showNotification(message) { // Simple alert for demo alert(message); } // Modal functions function showLoginModal() { document.getElementById('loginModal').classList.remove('hidden'); } function closeModal() { document.getElementById('loginModal').classList.add('hidden'); document.getElementById('quickViewModal').classList.add('hidden'); } function toggleDropdown() { document.getElementById('userDropdown').classList.toggle('hidden'); } // Close dropdown when clicking outside document.addEventListener('click', function(event) { const dropdown = document.getElementById('userDropdown'); if (dropdown && !dropdown.contains(event.target) && !event.target.closest('button')) { dropdown.classList.add('hidden'); } }); </script> </body> </html>

PROJECT STRUCTURE

ecommerce-platform/ │ ├── index.html # Main store front ├── product.html # Product detail page ├── category.html # Category listing ├── search.html # Search results ├── cart.html # Shopping cart ├── checkout.html # Checkout process ├── orders.html # Order history ├── profile.html # User profile │ ├── config.php # Database configuration ├── helpers.php # Helper functions │ ├── classes/ │ ├── ProductRecommender.php # Recommendation engine │ ├── Product.php # Product management │ ├── Cart.php # Shopping cart │ ├── Order.php # Order management │ └── Address.php # Address management │ ├── api/ │ ├── auth.php # Authentication endpoints │ ├── products.php # Product endpoints │ ├── recommendations.php # Recommendation endpoints │ ├── cart.php # Cart endpoints │ ├── orders.php # Order endpoints │ ├── addresses.php # Address endpoints │ └── categories.php # Category endpoints │ ├── assets/ │ ├── images/ │ │ ├── default-avatar.png │ │ └── no-image.png │ └── css/ │ └── style.css │ └── uploads/ ├── products/ ├── categories/ └── profiles/

FEATURES SUMMARY

✅ User Features

  • User registration & login
  • Profile management
  • Address management
  • Order history
  • Wishlist
  • Product reviews

✅ Product Features

  • Product browsing & search
  • Category filtering
  • Product images gallery
  • Price & attribute display
  • Stock status
  • Related products

✅ Shopping Cart

  • Add/remove items
  • Update quantities
  • Price calculations
  • Tax & shipping
  • Persistent cart

✅ Checkout

  • Address selection
  • Order summary
  • Multiple payment methods
  • Order confirmation
  • Email notifications

✅ Recommendation System

  • Personalized recommendations
  • Similar products
  • Frequently bought together
  • Trending products
  • Popular products
  • New arrivals
  • Category-based suggestions

✅ Recommendation Algorithms

  • Content-based filtering
  • Collaborative filtering
  • Popularity-based
  • Trending detection
  • Attribute matching
  • Purchase history analysis

✅ Admin Features

  • Product management
  • Order management
  • User management
  • Category management
  • Sales analytics
  • Inventory tracking

This complete e-commerce platform includes a sophisticated content-based recommendation system that analyzes user behavior, product attributes, and purchase patterns to provide personalized product suggestions, increasing engagement and sales.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper