exec( 'CREATE TABLE IF NOT EXISTS categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(120) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); $pdo->exec( 'CREATE TABLE IF NOT EXISTS products ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(160) NOT NULL, description TEXT NOT NULL, price DECIMAL(10,2) NOT NULL, stock INT NOT NULL DEFAULT 0, rating DECIMAL(3,2) NOT NULL DEFAULT 0.00, category_id INT NULL, image_url VARCHAR(255) NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX (category_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); $pdo->exec( 'CREATE TABLE IF NOT EXISTS orders ( id INT AUTO_INCREMENT PRIMARY KEY, order_number VARCHAR(20) NOT NULL, customer_name VARCHAR(120) NOT NULL, customer_email VARCHAR(160) NOT NULL, customer_address VARCHAR(255) NOT NULL, status VARCHAR(30) NOT NULL, total_price DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY (order_number) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); $pdo->exec( 'CREATE TABLE IF NOT EXISTS order_items ( id INT AUTO_INCREMENT PRIMARY KEY, order_id INT NOT NULL, product_id INT NOT NULL, quantity INT NOT NULL, price DECIMAL(10,2) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, INDEX (order_id), INDEX (product_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4' ); } function seed_data(): void { $pdo = db(); $count = (int) $pdo->query('SELECT COUNT(*) FROM products')->fetchColumn(); if ($count > 0) { return; } $categories = [ 'Apparel', 'Electronics', 'Moroccan Goods' ]; $categoryStmt = $pdo->prepare('INSERT INTO categories (name) VALUES (:name)'); $categoryIds = []; foreach ($categories as $name) { $categoryStmt->execute([':name' => $name]); $categoryIds[$name] = (int) $pdo->lastInsertId(); } $products = [ [ 'name' => 'Atlas Hoodie', 'description' => 'Midweight cotton hoodie with brushed interior and clean stitch detailing.', 'price' => 79.00, 'stock' => 24, 'rating' => 4.6, 'category' => 'Apparel' ], [ 'name' => 'Casablanca Sneakers', 'description' => 'Everyday sneaker with cushioned sole and minimalist profile.', 'price' => 98.00, 'stock' => 18, 'rating' => 4.4, 'category' => 'Apparel' ], [ 'name' => 'Rabat Smartwatch', 'description' => 'Slim fitness watch with sleep tracking and a 7-day battery.', 'price' => 149.00, 'stock' => 12, 'rating' => 4.2, 'category' => 'Electronics' ], [ 'name' => 'Sahara Bluetooth Speaker', 'description' => 'Portable speaker with clean bass and long-lasting battery.', 'price' => 129.00, 'stock' => 10, 'rating' => 4.3, 'category' => 'Electronics' ], [ 'name' => 'Fez Copper Lamp', 'description' => 'Hand-finished lamp with soft perforated light pattern.', 'price' => 119.00, 'stock' => 7, 'rating' => 4.8, 'category' => 'Moroccan Goods' ], [ 'name' => 'Agadir Ceramic Tagine', 'description' => 'Glazed ceramic tagine for slow cooking and elegant serving.', 'price' => 84.00, 'stock' => 9, 'rating' => 4.7, 'category' => 'Moroccan Goods' ] ]; $productStmt = $pdo->prepare( 'INSERT INTO products (name, description, price, stock, rating, category_id) VALUES (:name, :description, :price, :stock, :rating, :category_id)' ); foreach ($products as $product) { $productStmt->execute([ ':name' => $product['name'], ':description' => $product['description'], ':price' => $product['price'], ':stock' => $product['stock'], ':rating' => $product['rating'], ':category_id' => $categoryIds[$product['category']] ?? null ]); } } function get_categories(): array { $stmt = db()->query('SELECT id, name FROM categories ORDER BY name ASC'); return $stmt->fetchAll(); } function get_products(?string $search = null, ?int $categoryId = null, ?string $sort = null): array { $sql = 'SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE 1=1'; $params = []; if ($search) { $sql .= ' AND (p.name LIKE :search OR p.description LIKE :search)'; $params[':search'] = '%' . $search . '%'; } if ($categoryId) { $sql .= ' AND p.category_id = :category_id'; $params[':category_id'] = $categoryId; } switch ($sort) { case 'price_asc': $sql .= ' ORDER BY p.price ASC'; break; case 'price_desc': $sql .= ' ORDER BY p.price DESC'; break; default: $sql .= ' ORDER BY p.created_at DESC'; break; } $stmt = db()->prepare($sql); $stmt->execute($params); return $stmt->fetchAll(); } function get_product(int $id): ?array { $stmt = db()->prepare('SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.id = :id'); $stmt->execute([':id' => $id]); $product = $stmt->fetch(); return $product ?: null; } function format_price(float $price): string { return '$' . number_format($price, 2); } function product_image_data(string $label): string { $clean = preg_replace('/[^A-Za-z0-9]/', '', $label); $text = strtoupper(substr($clean, 0, 2)); if ($text === '') { $text = 'ES'; } $svg = "" . $text . ""; return 'data:image/svg+xml;utf8,' . rawurlencode($svg); } function cart_count(): int { $cart = $_SESSION['cart'] ?? []; return array_sum($cart); } function cart_items(): array { $cart = $_SESSION['cart'] ?? []; if (!$cart) { return []; } $ids = array_keys($cart); $placeholders = implode(',', array_fill(0, count($ids), '?')); $stmt = db()->prepare( "SELECT p.*, c.name AS category_name FROM products p LEFT JOIN categories c ON c.id = p.category_id WHERE p.id IN ($placeholders)" ); $stmt->execute($ids); $products = $stmt->fetchAll(); $indexed = []; foreach ($products as $product) { $indexed[(int) $product['id']] = $product; } $items = []; foreach ($cart as $productId => $qty) { $product = $indexed[(int) $productId] ?? null; if (!$product) { continue; } $lineTotal = ((float) $product['price']) * $qty; $items[] = [ 'product' => $product, 'quantity' => $qty, 'line_total' => $lineTotal ]; } return $items; } function cart_total(): float { $total = 0.0; foreach (cart_items() as $item) { $total += $item['line_total']; } return $total; } function cart_add(int $productId, int $quantity): void { if ($quantity < 1) { return; } $cart = $_SESSION['cart'] ?? []; $cart[$productId] = ($cart[$productId] ?? 0) + $quantity; $_SESSION['cart'] = $cart; } function cart_update(int $productId, int $quantity): void { $cart = $_SESSION['cart'] ?? []; if ($quantity < 1) { unset($cart[$productId]); } else { $cart[$productId] = $quantity; } $_SESSION['cart'] = $cart; } function cart_clear(): void { unset($_SESSION['cart']); } function create_order(array $customer, array $items): ?string { if (!$items) { return null; } $pdo = db(); $pdo->beginTransaction(); try { $orderNumber = 'ES9-' . strtoupper(bin2hex(random_bytes(3))); $total = 0.0; foreach ($items as $item) { $total += $item['line_total']; } $orderStmt = $pdo->prepare( 'INSERT INTO orders (order_number, customer_name, customer_email, customer_address, status, total_price) VALUES (:order_number, :customer_name, :customer_email, :customer_address, :status, :total_price)' ); $orderStmt->execute([ ':order_number' => $orderNumber, ':customer_name' => $customer['name'], ':customer_email' => $customer['email'], ':customer_address' => $customer['address'], ':status' => 'Processing', ':total_price' => $total ]); $orderId = (int) $pdo->lastInsertId(); $itemStmt = $pdo->prepare( 'INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (:order_id, :product_id, :quantity, :price)' ); foreach ($items as $item) { $itemStmt->execute([ ':order_id' => $orderId, ':product_id' => $item['product']['id'], ':quantity' => $item['quantity'], ':price' => $item['product']['price'] ]); } $pdo->commit(); return $orderNumber; } catch (Throwable $e) { $pdo->rollBack(); return null; } } function get_order_by_number(string $orderNumber): ?array { $stmt = db()->prepare('SELECT * FROM orders WHERE order_number = :order_number'); $stmt->execute([':order_number' => $orderNumber]); $order = $stmt->fetch(); return $order ?: null; } function get_order_items(int $orderId): array { $stmt = db()->prepare( 'SELECT oi.*, p.name, p.description FROM order_items oi LEFT JOIN products p ON p.id = oi.product_id WHERE oi.order_id = :order_id' ); $stmt->execute([':order_id' => $orderId]); return $stmt->fetchAll(); } function get_orders(): array { $stmt = db()->query('SELECT * FROM orders ORDER BY created_at DESC'); return $stmt->fetchAll(); } function update_order_status(int $orderId, string $status): void { $stmt = db()->prepare('UPDATE orders SET status = :status WHERE id = :id'); $stmt->execute([':status' => $status, ':id' => $orderId]); }