1. 12:00 06/08/2026
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']) ?>">« <?= 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