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