Tiếp Phần 3 (đã có bộ khung + giao diện chung). Hôm nay bắt đầu hiển thị dữ liệu thật: trang chủ, trang danh mục, trang chi tiết sản phẩm và trang tìm kiếm. ============================== 1. FILE index.php (trang chủ) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $page_title = 'Trang chu'; // Lay danh sach danh muc de hien thi thanh menu loc $categories = $pdo->query('SELECT id, name, slug FROM categories ORDER BY name ASC')->fetchAll(); // Lay 12 san pham dang ban, moi nhat truoc $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE is_active = 1 ORDER BY created_at DESC LIMIT 12' ); $stmt->execute(); $products = $stmt->fetchAll(); require __DIR__ . '/includes/header.php'; ?> <div class="category-bar"> <a href="index.php" class="active">Tat ca</a> <?php foreach ($categories as $cat): ?> <a href="category.php?slug=<?= e($cat['slug']) ?>"><?= e($cat['name']) ?></a> <?php endforeach; ?> </div> <h1 class="page-title">San pham noi bat</h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <?php if ($p['sale_price']): ?> <span class="price"><?= format_price($p['sale_price']) ?></span> <span class="price-old"><?= format_price($p['price']) ?></span> <?php else: ?> <span class="price"><?= format_price($p['price']) ?></span> <?php endif; ?> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Chua co san pham nao.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 2. FILE category.php (sản phẩm theo danh mục) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $slug = $_GET['slug'] ?? ''; $stmt = $pdo->prepare('SELECT * FROM categories WHERE slug = ? LIMIT 1'); $stmt->execute([$slug]); $category = $stmt->fetch(); if (!$category) { http_response_code(404); die('Khong tim thay danh muc.'); } $page_title = $category['name']; $categories = $pdo->query('SELECT id, name, slug FROM categories ORDER BY name ASC')->fetchAll(); $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE category_id = ? AND is_active = 1 ORDER BY created_at DESC' ); $stmt->execute([$category['id']]); $products = $stmt->fetchAll(); require __DIR__ . '/includes/header.php'; ?> <div class="category-bar"> <a href="index.php">Tat ca</a> <?php foreach ($categories as $cat): ?> <a href="category.php?slug=<?= e($cat['slug']) ?>" class="<?= $cat['id'] === $category['id'] ? 'active' : '' ?>"><?= e($cat['name']) ?></a> <?php endforeach; ?> </div> <h1 class="page-title"><?= e($category['name']) ?></h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <?php if ($p['sale_price']): ?> <span class="price"><?= format_price($p['sale_price']) ?></span> <span class="price-old"><?= format_price($p['price']) ?></span> <?php else: ?> <span class="price"><?= format_price($p['price']) ?></span> <?php endif; ?> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Danh muc nay chua co san pham.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 3. FILE product.php (chi tiết sản phẩm) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $slug = $_GET['slug'] ?? ''; $stmt = $pdo->prepare( 'SELECT p.*, c.name AS category_name, c.slug AS category_slug FROM products p JOIN categories c ON c.id = p.category_id WHERE p.slug = ? AND p.is_active = 1 LIMIT 1' ); $stmt->execute([$slug]); $product = $stmt->fetch(); if (!$product) { http_response_code(404); die('Khong tim thay san pham.'); } $page_title = $product['name']; $final_price = $product['sale_price'] ?: $product['price']; require __DIR__ . '/includes/header.php'; ?> <p><a href="category.php?slug=<?= e($product['category_slug']) ?>">&laquo; <?= e($product['category_name']) ?></a></p> <div class="product-detail" style="display:flex; gap:30px; flex-wrap:wrap; background:#fff; padding:20px; border-radius:8px; margin-top:14px;"> <div style="flex:1; min-width:280px;"> <img src="assets/uploads/products/<?= e($product['image'] ?: 'no-image.png') ?>" alt="<?= e($product['name']) ?>" style="width:100%; border-radius:6px;"> </div> <div style="flex:1; min-width:280px;"> <h1 class="page-title"><?= e($product['name']) ?></h1> <p style="margin:10px 0;"> <span class="price" style="font-size:24px;"><?= format_price($final_price) ?></span> <?php if ($product['sale_price']): ?> <span class="price-old" style="font-size:16px;"><?= format_price($product['price']) ?></span> <?php endif; ?> </p> <p>Con hang: <?= (int) $product['stock'] ?> san pham</p> <p style="margin:14px 0; color:#555;"><?= nl2br(e($product['description'])) ?></p> <?php if ($product['stock'] > 0): ?> <form action="cart_add.php" method="post" style="display:flex; gap:10px; align-items:center;"> <?= csrf_field() ?> <input type="hidden" name="product_id" value="<?= (int) $product['id'] ?>"> <input type="number" name="quantity" value="1" min="1" max="<?= (int) $product['stock'] ?>" style="width:70px; padding:8px;"> <button type="submit" class="btn">Them vao gio hang</button> </form> <?php else: ?> <p style="color:#e53935; font-weight:bold;">San pham tam het hang</p> <?php endif; ?> </div> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== 4. FILE search.php (tìm kiếm sản phẩm) ============================== <?php require_once __DIR__ . '/includes/functions.php'; $q = trim($_GET['q'] ?? ''); $page_title = 'Ket qua tim kiem: ' . $q; $products = []; if ($q !== '') { $stmt = $pdo->prepare( 'SELECT id, name, slug, price, sale_price, image FROM products WHERE is_active = 1 AND name LIKE :q ORDER BY created_at DESC' ); $stmt->execute([':q' => '%' . $q . '%']); $products = $stmt->fetchAll(); } require __DIR__ . '/includes/header.php'; ?> <h1 class="page-title">Ket qua tim kiem cho "<?= e($q) ?>"</h1> <div class="product-grid"> <?php foreach ($products as $p): ?> <a href="product.php?slug=<?= e($p['slug']) ?>" class="product-card"> <img src="assets/uploads/products/<?= e($p['image'] ?: 'no-image.png') ?>" alt="<?= e($p['name']) ?>"> <div class="info"> <div class="name"><?= e($p['name']) ?></div> <span class="price"><?= format_price($p['sale_price'] ?: $p['price']) ?></span> </div> </a> <?php endforeach; ?> <?php if (empty($products)): ?> <p>Khong tim thay san pham phu hop.</p> <?php endif; ?> </div> <?php require __DIR__ . '/includes/footer.php'; ?> ============================== GHI CHÚ QUAN TRỌNG VỀ BẢO MẬT (áp dụng ngay từ bây giờ) ============================== - Mọi câu SQL đều dùng $pdo->prepare() + execute([...]) (prepared statement), tuyệt đối không nối chuỗi kiểu "WHERE slug = '$slug'" - đó là lỗ hổng SQL Injection kinh điển. - Mọi dữ liệu từ CSDL khi in ra HTML đều bọc qua e() (trừ trường hợp cố ý cho phép xuống dòng như mô tả sản phẩm thì dùng nl2br(e(...)) - vẫn escape trước rồi mới nl2br, thứ tự này không được đảo ngược). - File "no-image.png" các bạn tự tìm 1 ảnh placeholder bất kỳ (kích thước vuông, VD 400x400) bỏ vào assets/uploads/products/no-image.png để không bị lỗi ảnh vỡ khi sản phẩm chưa có ảnh riêng. Phần 5 tiếp theo: xây dựng giỏ hàng hoàn chỉnh bằng session (thêm, sửa số lượng, xoá sản phẩm khỏi giỏ, tính tổng tiền). Hẹn gặp lại anh em! --- Hết Phần 4 --- ---------------------------------------- MỤC LỤC LOẠT BÀI: 1. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 1: Tổng Quan Dự Án, Yêu Cầu Môi Trường & Cấu Trúc Thư Mục - https://diendan.anyvlogs.info/chu-de/457-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-1-tong-quan-du-an-yeu-cau-moi-truong-cau-truc-thu-muc 2. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 2: Thiết Kế Cơ Sở Dữ Liệu (Database) Đầy Đủ Cho Website Bán Hàng - https://diendan.anyvlogs.info/chu-de/458-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-2-thiet-ke-co-so-du-lieu-database-day-du-cho-website-ban-hang 3. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 3: Dựng Khung Ứng Dụng - Kết Nối PDO, Hàm Dùng Chung, Giao Diện Chung - https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung >> 4. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 4: Trang Chủ, Danh Mục Sản Phẩm & Chi Tiết Sản Phẩm - https://diendan.anyvlogs.info/chu-de/460-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-4-trang-chu-danh-muc-san-pham-chi-tiet-san-pham 5. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 5: Xây Dựng Giỏ Hàng (Cart) Bằng Session - https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session 6. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 6: Đăng Ký, Đăng Nhập, Đăng Xuất Thành Viên - https://diendan.anyvlogs.info/chu-de/462-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-6-dang-ky-dang-nhap-dang-xuat-thanh-vien 7. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 7: Đặt Hàng, Thanh Toán COD & Lịch Sử Đơn Hàng - https://diendan.anyvlogs.info/chu-de/463-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-7-dat-hang-thanh-toan-cod-lich-su-don-hang 8. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 8: Trang Quản Trị (Admin) - Quản Lý Danh Mục, Sản Phẩm, Đơn Hàng - https://diendan.anyvlogs.info/chu-de/464-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-8-trang-quan-tri-admin-quan-ly-danh-muc-san-pham-don-hang 9. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 9: Bảo Mật Website (CSRF, XSS, SQL Injection, Upload An Toàn) - https://diendan.anyvlogs.info/chu-de/465-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-9-bao-mat-website-csrf-xss-sql-injection-upload-an-toan 10. [Series PHP+MySQL] Làm Website Bán Hàng Từ A-Z - Phần 10 (Kết Thúc): Tối Ưu Hiệu Năng & Triển Khai Lên Hosting Thật - https://diendan.anyvlogs.info/chu-de/466-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-10-ket-thuc-toi-uu-hieu-nang-trien-khai-len-hosting-that Phần trước: https://diendan.anyvlogs.info/chu-de/459-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-3-dung-khung-ung-dung-ket-noi-pdo-ham-dung-chung-giao-dien-chung Phần sau: https://diendan.anyvlogs.info/chu-de/461-series-php-mysql-lam-website-ban-hang-tu-a-z-phan-5-xay-dung-gio-hang-cart-bang-session