PHP- Use Case 19: Developing an E-commerce Platform with PHP and MySQL

Project Overview

An E-commerce Platform enables businesses to sell products or services online. This project covers building a fully functional e-commerce website using PHP and MySQL, incorporating features like user registration and login, product listings, shopping cart, order management, and payment processing. Building such a platform will provide your followers with hands-on experience in creating scalable, secure, and user-friendly web applications.

Prerequisites

Ensure you have the following:

  • Web Server: Apache (using XAMPP, WAMP, or MAMP)
  • PHP: Version 7.4 or higher
  • MySQL: For database management
  • Code Editor: VS Code, Sublime Text, PHPStorm, etc.
  • Composer: For dependency management
  • Basic Understanding of Sessions, Forms, CRUD Operations, and Object-Oriented Programming (OOP) in PHP
  • Familiarity with HTML, CSS, and JavaScript

Step-by-Step Procedure

1. Setting Up the Development Environment

  1. Install XAMPP:
    • Download from XAMPP Official Website.
    • Follow the installation wizard and install it in the default directory.
  2. Start Apache and MySQL:
    • Open the XAMPP Control Panel.
    • Start the Apache and MySQL modules.
  3. Install Composer:

2. Creating the Database

  1. Access phpMyAdmin:
    • Navigate to http://localhost/phpmyadmin/ in your browser.
  2. Create a New Database:
    • Click on “New” in the left sidebar.
    • Name the database ecommerce.
    • Choose “utf8mb4_unicode_ci” as the collation.
    • Click “Create”.
  3. Create Tables:
    • users Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT username VARCHAR(50) NO UNI NULL email VARCHAR(100) NO UNI NULL password VARCHAR(255) NO NULL role ENUM(‘customer’,’admin’) NO ‘customer’ created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • products Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT name VARCHAR(255) NO NULL description TEXT NO NULL price DECIMAL(10,2) NO NULL image VARCHAR(255) YES NULL stock INT NO 0 created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • categories Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT name VARCHAR(100) NO UNI NULL created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • product_categories Table: (Pivot table for many-to-many relationship) Field Type Null Key Default Extra product_id INT NO MUL NULL category_id INT NO MUL NULL
      • Composite Primary Key: (product_id, category_id)
    • orders Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT user_id INT NO MUL NULL total_amount DECIMAL(10,2) NO 0.00 status ENUM(‘pending’,’completed’,’cancelled’) NO ‘pending’ created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • order_items Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT order_id INT NO MUL NULL product_id INT NO MUL NULL quantity INT NO 1 price DECIMAL(10,2) NO 0.00
  4. Establish Foreign Keys:
    • products.id references order_items.product_id
    • users.id references orders.user_id
    • orders.id references order_items.order_id
    • products.id references product_categories.product_id
    • categories.id references product_categories.category_id

3. Project Structure

Organize your project files as follows:

ecommerce/
├── vendor/                 # Composer dependencies
├── config/
│   └── db.php
├── public/
│   ├── index.php
│   ├── login.php
│   ├── register.php
│   ├── logout.php
│   ├── dashboard.php
│   ├── products/
│   │   ├── create.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── categories/
│   │   ├── create.php
│   │   ├── edit.php
│   │   └── delete.php
│   ├── cart.php
│   ├── checkout.php
│   ├── orders.php
│   ├── assets/
│   │   ├── css/
│   │   │   └── styles.css
│   │   ├── images/
│   │   │   └── (product images)
│   │   └── js/
│   │       └── scripts.js
├── src/
│   ├── Controller.php
│   ├── UserController.php
│   ├── ProductController.php
│   ├── CategoryController.php
│   ├── CartController.php
│   ├── OrderController.php
│   └── models/
│       ├── User.php
│       ├── Product.php
│       ├── Category.php
│       ├── Cart.php
│       ├── Order.php
│       └── OrderItem.php
├── templates/
│   ├── header.php
│   └── footer.php
├── .htaccess
├── composer.json
├── composer.lock
├── README.md
└── index.php

4. Configuration

a. Database Connection (config/db.php)

Create a file named db.php inside the config directory to handle database connections.

<?php
// config/db.php

$host = 'localhost';
$db   = 'ecommerce';
$user = 'root'; // Default XAMPP MySQL user
$pass = '';     // Default XAMPP MySQL password is empty
$charset = 'utf8mb4';

$dsn = "mysql:host=$host;dbname=$db;charset=$charset";

$options = [
    PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION, // Enable exceptions
    PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,       // Fetch associative arrays
    PDO::ATTR_EMULATE_PREPARES   => false,                  // Disable emulation
];

try {
    $pdo = new PDO($dsn, $user, $pass, $options);
} catch (\PDOException $e) {
    // Handle connection errors
    http_response_code(500);
    echo "Database connection failed.";
    exit;
}
?>
b. URL Rewriting (.htaccess)

To route all requests to public/index.php, create an .htaccess file in the root directory.

# .htaccess

RewriteEngine On
RewriteRule ^(.*)$ public/$1 [L]
c. Composer Autoloading

Ensure that Composer’s autoloader is included in your project. In your public/index.php, add:

<?php
// public/index.php

require_once __DIR__ . '/../vendor/autoload.php';
require_once __DIR__ . '/../templates/header.php';

// Your homepage content here

require_once __DIR__ . '/../templates/footer.php';
?>

5. Creating Reusable Templates

a. Header (templates/header.php)
<?php
// templates/header.php
session_start();
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>My E-commerce Platform</title>
    <link rel="stylesheet" href="/ecommerce/public/assets/css/styles.css">
</head>
<body>
    <header>
        <h1>My E-commerce Store</h1>
        <nav>
            <a href="/ecommerce/public/index.php">Home</a>
            <a href="/ecommerce/public/products/create.php">Add Product</a>
            <a href="/ecommerce/public/categories/create.php">Add Category</a>
            <a href="/ecommerce/public/cart.php">Cart</a>
            <?php if (isset($_SESSION['user_id'])): ?>
                <a href="/ecommerce/public/dashboard.php">Dashboard</a>
                <a href="/ecommerce/public/logout.php">Logout (<?php echo htmlspecialchars($_SESSION['username']); ?>)</a>
            <?php else: ?>
                <a href="/ecommerce/public/register.php">Register</a>
                <a href="/ecommerce/public/login.php">Login</a>
            <?php endif; ?>
        </nav>
    </header>
    <main>
b. Footer (templates/footer.php)
<?php
// templates/footer.php
?>
    </main>
    <footer>
        <p>&copy; <?php echo date("Y"); ?> My E-commerce Store</p>
    </footer>
    <script src="/ecommerce/public/assets/js/scripts.js"></script>
</body>
</html>

6. Implementing User Authentication

a. User Registration (public/register.php)

Create a register.php file inside the public directory to handle user registration.

<?php
// public/register.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $username = trim($_POST['username']);
    $email    = trim($_POST['email']);
    $password = trim($_POST['password']);
    $confirm_password = trim($_POST['confirm_password']);

    if (empty($username)) {
        $errors[] = 'Username is required.';
    }

    if (empty($email)) {
        $errors[] = 'Email is required.';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Invalid email format.';
    }

    if (empty($password)) {
        $errors[] = 'Password is required.';
    } elseif (strlen($password) < 6) {
        $errors[] = 'Password must be at least 6 characters.';
    }

    if ($password !== $confirm_password) {
        $errors[] = 'Passwords do not match.';
    }

    // Check if username or email already exists
    if (empty($errors)) {
        $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ? OR email = ?');
        $stmt->execute([$username, $email]);
        if ($stmt->fetch()) {
            $errors[] = 'Username or email already exists.';
        }
    }

    if (empty($errors)) {
        // Hash the password
        $hashed_password = password_hash($password, PASSWORD_DEFAULT);

        // Insert into database
        $stmt = $pdo->prepare('INSERT INTO users (username, email, password, role) VALUES (?, ?, ?, ?)');
        if ($stmt->execute([$username, $email, $hashed_password, 'customer'])) {
            $success = 'Registration successful. You can now <a href="login.php">login</a>.';
            // Clear form fields
            $username = $email = $password = $confirm_password = '';
        } else {
            $errors[] = 'There was an error registering. Please try again.';
        }
    }
}
?>

<h2>Register</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo $success; ?></p>
    </div>
<?php endif; ?>

<form action="register.php" method="POST">
    <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" value="<?php echo htmlspecialchars($username ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="email">Email:</label>
        <input type="email" id="email" name="email" value="<?php echo htmlspecialchars($email ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="password">Password (min 6 characters):</label>
        <input type="password" id="password" name="password" required>
    </div>

    <div class="form-group">
        <label for="confirm_password">Confirm Password:</label>
        <input type="password" id="confirm_password" name="confirm_password" required>
    </div>

    <button type="submit">Register</button>
</form>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
b. User Login (public/login.php)

Create a login.php file inside the public directory to handle user login.

<?php
// public/login.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $username = trim($_POST['username']);
    $password = trim($_POST['password']);

    if (empty($username)) {
        $errors[] = 'Username is required.';
    }

    if (empty($password)) {
        $errors[] = 'Password is required.';
    }

    if (empty($errors)) {
        // Fetch user from database
        $stmt = $pdo->prepare('SELECT * FROM users WHERE username = ?');
        $stmt->execute([$username]);
        $user = $stmt->fetch();

        if ($user && password_verify($password, $user['password'])) {
            // Password is correct, start a session
            $_SESSION['user_id'] = $user['id'];
            $_SESSION['username'] = $user['username'];
            $_SESSION['role'] = $user['role'];
            // Redirect to dashboard
            header('Location: dashboard.php');
            exit;
        } else {
            $errors[] = 'Invalid username or password.';
        }
    }
}
?>

<h2>Login</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="login.php" method="POST">
    <div class="form-group">
        <label for="username">Username:</label>
        <input type="text" id="username" name="username" value="<?php echo htmlspecialchars($username ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="password">Password:</label>
        <input type="password" id="password" name="password" required>
    </div>

    <button type="submit">Login</button>
</form>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
c. User Logout (public/logout.php)

Create a logout.php file inside the public directory to handle user logout.

<?php
// public/logout.php

session_start();
session_unset();
session_destroy();

// Redirect to login page
header('Location: login.php');
exit;
?>

7. Implementing the Dashboard (public/dashboard.php)

Create a dashboard.php file inside the public directory to serve as the main interface for managing products, categories, and orders.

<?php
// public/dashboard.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

$user_role = $_SESSION['role'];
?>

<h2>Dashboard</h2>

<?php if ($user_role === 'admin'): ?>
    <p>Welcome, Admin! You have full access to manage the e-commerce platform.</p>
    <ul>
        <li><a href="products/create.php">Add New Product</a></li>
        <li><a href="categories/create.php">Add New Category</a></li>
        <li><a href="orders.php">View Orders</a></li>
        <li><a href="manage_users.php">Manage Users</a></li>
    </ul>
<?php elseif ($user_role === 'editor'): ?>
    <p>Welcome, Editor! You can manage products and categories.</p>
    <ul>
        <li><a href="products/create.php">Add New Product</a></li>
        <li><a href="categories/create.php">Add New Category</a></li>
    </ul>
<?php else: ?>
    <p>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>! You can manage your own orders.</p>
    <ul>
        <li><a href="orders.php">View Your Orders</a></li>
    </ul>
<?php endif; ?>

<h3>Your Products</h3>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Price ($)</th>
            <th>Stock</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
    <?php
    if ($user_role === 'admin' || $user_role === 'editor') {
        // Admins and Editors can see all products
        $stmt = $pdo->query('SELECT * FROM products');
    } else {
        // Customers do not manage products
        $stmt = null;
    }

    if ($stmt):
        $products = $stmt->fetchAll();
        if ($products):
            foreach ($products as $product):
    ?>
                <tr>
                    <td><?php echo htmlspecialchars($product['id']); ?></td>
                    <td><?php echo htmlspecialchars($product['name']); ?></td>
                    <td><?php echo htmlspecialchars($product['price']); ?></td>
                    <td><?php echo htmlspecialchars($product['stock']); ?></td>
                    <td class="action-links">
                        <a href="products/edit.php?id=<?php echo $product['id']; ?>">Edit</a>
                        <a href="products/delete.php?id=<?php echo $product['id']; ?>" onclick="return confirm('Are you sure you want to delete this product?');">Delete</a>
                    </td>
                </tr>
    <?php
            endforeach;
        else:
            echo '<tr><td colspan="5">No products found.</td></tr>';
        endif;
    else:
        echo '<tr><td colspan="5">Access denied.</td></tr>';
    endif;
    ?>
    </tbody>
</table>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>

8. Managing Products

a. Creating a New Product (public/products/create.php)

Create a create.php file inside the public/products directory to handle product creation.

<?php
// public/products/create.php

require_once __DIR__ . '/../../config/db.php';
require_once __DIR__ . '/../../templates/header.php';

// Check if user is logged in and has permission to add products
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to add products.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

// Fetch all categories
$stmt = $pdo->query('SELECT * FROM categories');
$categories = $stmt->fetchAll();

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $name = trim($_POST['name']);
    $description = trim($_POST['description']);
    $price = trim($_POST['price']);
    $stock = trim($_POST['stock']);
    $selected_categories = $_POST['categories'] ?? [];

    if (empty($name)) {
        $errors[] = 'Product name is required.';
    }

    if (empty($description)) {
        $errors[] = 'Product description is required.';
    }

    if (empty($price) || !is_numeric($price) || $price < 0) {
        $errors[] = 'Valid product price is required.';
    }

    if (empty($stock) || !is_numeric($stock) || $stock < 0) {
        $errors[] = 'Valid stock quantity is required.';
    }

    if (empty($selected_categories)) {
        $errors[] = 'At least one category must be selected.';
    }

    // Handle image upload
    $image_path = null;
    if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
        $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
        if (!in_array($_FILES['image']['type'], $allowed_types)) {
            $errors[] = 'Only JPEG, PNG, and GIF images are allowed.';
        } elseif ($_FILES['image']['size'] > 2 * 1024 * 1024) { // 2MB limit
            $errors[] = 'Image size should not exceed 2MB.';
        } else {
            $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
            $image_filename = uniqid() . '.' . $ext;
            $image_destination = '/ecommerce/public/assets/images/' . $image_filename;
            if (move_uploaded_file($_FILES['image']['tmp_name'], __DIR__ . '/../../public/assets/images/' . $image_filename)) {
                $image_path = $image_filename;
            } else {
                $errors[] = 'Failed to upload image.';
            }
        }
    }

    if (empty($errors)) {
        // Insert product into database
        $stmt = $pdo->prepare('INSERT INTO products (name, description, price, image, stock) VALUES (?, ?, ?, ?, ?)');
        if ($stmt->execute([$name, $description, $price, $image_path, $stock])) {
            $product_id = $pdo->lastInsertId();
            // Assign categories to product
            $stmt = $pdo->prepare('INSERT INTO product_categories (product_id, category_id) VALUES (?, ?)');
            foreach ($selected_categories as $category_id) {
                $stmt->execute([$product_id, $category_id]);
            }
            $success = 'Product added successfully.';
            // Clear form fields
            $name = $description = $price = $stock = '';
            $selected_categories = [];
        } else {
            $errors[] = 'There was an error adding the product.';
        }
    }
}
?>

<h2>Add New Product</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="create.php" method="POST" enctype="multipart/form-data">
    <div class="form-group">
        <label for="name">Product Name:</label>
        <input type="text" id="name" name="name" value="<?php echo htmlspecialchars($name ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="description">Product Description:</label>
        <textarea id="description" name="description" rows="5" required><?php echo htmlspecialchars($description ?? ''); ?></textarea>
    </div>

    <div class="form-group">
        <label for="price">Price ($):</label>
        <input type="number" step="0.01" id="price" name="price" value="<?php echo htmlspecialchars($price ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="stock">Stock Quantity:</label>
        <input type="number" id="stock" name="stock" value="<?php echo htmlspecialchars($stock ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="image">Product Image (optional):</label>
        <input type="file" id="image" name="image" accept="image/*">
    </div>

    <div class="form-group">
        <label for="categories">Select Categories:</label>
        <select id="categories" name="categories[]" multiple required>
            <?php foreach ($categories as $category): ?>
                <option value="<?php echo $category['id']; ?>" <?php if (isset($selected_categories) && in_array($category['id'], $selected_categories)) echo 'selected'; ?>>
                    <?php echo htmlspecialchars($category['name']); ?>
                </option>
            <?php endforeach; ?>
        </select>
    </div>

    <button type="submit">Add Product</button>
</form>

<?php
require_once __DIR__ . '/../../templates/footer.php';
?>
b. Editing a Product (public/products/edit.php)

Create an edit.php file inside the public/products directory to handle product editing.

<?php
// public/products/edit.php

require_once __DIR__ . '/../../config/db.php';
require_once __DIR__ . '/../../templates/header.php';

// Check if user is logged in and has permission to edit products
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to edit products.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

// Get product ID from URL
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

// Fetch product details
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$product_id]);
$product = $stmt->fetch();

if (!$product) {
    echo "<p>Product not found.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

// Fetch categories
$stmt = $pdo->query('SELECT * FROM categories');
$categories = $stmt->fetchAll();

// Fetch categories assigned to the product
$stmt = $pdo->prepare('SELECT category_id FROM product_categories WHERE product_id = ?');
$stmt->execute([$product_id]);
$assigned_categories = $stmt->fetchAll(PDO::FETCH_COLUMN);

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $name = trim($_POST['name']);
    $description = trim($_POST['description']);
    $price = trim($_POST['price']);
    $stock = trim($_POST['stock']);
    $selected_categories = $_POST['categories'] ?? [];

    if (empty($name)) {
        $errors[] = 'Product name is required.';
    }

    if (empty($description)) {
        $errors[] = 'Product description is required.';
    }

    if (empty($price) || !is_numeric($price) || $price < 0) {
        $errors[] = 'Valid product price is required.';
    }

    if (empty($stock) || !is_numeric($stock) || $stock < 0) {
        $errors[] = 'Valid stock quantity is required.';
    }

    if (empty($selected_categories)) {
        $errors[] = 'At least one category must be selected.';
    }

    // Handle image upload
    $image_path = $product['image'];
    if (isset($_FILES['image']) && $_FILES['image']['error'] === UPLOAD_ERR_OK) {
        $allowed_types = ['image/jpeg', 'image/png', 'image/gif'];
        if (!in_array($_FILES['image']['type'], $allowed_types)) {
            $errors[] = 'Only JPEG, PNG, and GIF images are allowed.';
        } elseif ($_FILES['image']['size'] > 2 * 1024 * 1024) { // 2MB limit
            $errors[] = 'Image size should not exceed 2MB.';
        } else {
            $ext = pathinfo($_FILES['image']['name'], PATHINFO_EXTENSION);
            $image_filename = uniqid() . '.' . $ext;
            $image_destination = '/ecommerce/public/assets/images/' . $image_filename;
            if (move_uploaded_file($_FILES['image']['tmp_name'], __DIR__ . '/../../public/assets/images/' . $image_filename)) {
                // Delete old image if exists
                if ($product['image'] && file_exists(__DIR__ . '/../../public/assets/images/' . $product['image'])) {
                    unlink(__DIR__ . '/../../public/assets/images/' . $product['image']);
                }
                $image_path = $image_filename;
            } else {
                $errors[] = 'Failed to upload image.';
            }
        }
    }

    if (empty($errors)) {
        // Update product in database
        $stmt = $pdo->prepare('UPDATE products SET name = ?, description = ?, price = ?, image = ?, stock = ? WHERE id = ?');
        if ($stmt->execute([$name, $description, $price, $image_path, $stock, $product_id])) {
            // Update categories
            // First, delete existing categories
            $stmt = $pdo->prepare('DELETE FROM product_categories WHERE product_id = ?');
            $stmt->execute([$product_id]);

            // Assign new categories
            $stmt = $pdo->prepare('INSERT INTO product_categories (product_id, category_id) VALUES (?, ?)');
            foreach ($selected_categories as $category_id) {
                $stmt->execute([$product_id, $category_id]);
            }

            $success = 'Product updated successfully.';
            // Refresh product data
            $stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
            $stmt->execute([$product_id]);
            $product = $stmt->fetch();

            $assigned_categories = $selected_categories;
        } else {
            $errors[] = 'There was an error updating the product.';
        }
    }
}
?>

<h2>Edit Product</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="edit.php?id=<?php echo $product_id; ?>" method="POST" enctype="multipart/form-data">
    <div class="form-group">
        <label for="name">Product Name:</label>
        <input type="text" id="name" name="name" value="<?php echo htmlspecialchars($product['name']); ?>" required>
    </div>

    <div class="form-group">
        <label for="description">Product Description:</label>
        <textarea id="description" name="description" rows="5" required><?php echo htmlspecialchars($product['description']); ?></textarea>
    </div>

    <div class="form-group">
        <label for="price">Price ($):</label>
        <input type="number" step="0.01" id="price" name="price" value="<?php echo htmlspecialchars($product['price']); ?>" required>
    </div>

    <div class="form-group">
        <label for="stock">Stock Quantity:</label>
        <input type="number" id="stock" name="stock" value="<?php echo htmlspecialchars($product['stock']); ?>" required>
    </div>

    <div class="form-group">
        <label for="image">Product Image (optional):</label>
        <?php if ($product['image']): ?>
            <img src="/ecommerce/public/assets/images/<?php echo htmlspecialchars($product['image']); ?>" alt="Product Image" style="max-width:200px;">
            <br>
        <?php endif; ?>
        <input type="file" id="image" name="image" accept="image/*">
    </div>

    <div class="form-group">
        <label for="categories">Select Categories:</label>
        <select id="categories" name="categories[]" multiple required>
            <?php foreach ($categories as $category): ?>
                <option value="<?php echo $category['id']; ?>" <?php if (in_array($category['id'], $assigned_categories)) echo 'selected'; ?>>
                    <?php echo htmlspecialchars($category['name']); ?>
                </option>
            <?php endforeach; ?>
        </select>
    </div>

    <button type="submit">Update Product</button>
</form>

<?php
require_once __DIR__ . '/../../templates/footer.php';
?>
c. Deleting a Product (public/products/delete.php)

Create a delete.php file inside the public/products directory to handle product deletion.

<?php
// public/products/delete.php

require_once __DIR__ . '/../../config/db.php';

// Check if user is logged in and has permission to delete products
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to delete products.</p>";
    exit;
}

// Get product ID from URL
$product_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

// Fetch product details
$stmt = $pdo->prepare('SELECT * FROM products WHERE id = ?');
$stmt->execute([$product_id]);
$product = $stmt->fetch();

if (!$product) {
    echo "<p>Product not found.</p>";
    exit;
}

// Delete product categories
$stmt = $pdo->prepare('DELETE FROM product_categories WHERE product_id = ?');
$stmt->execute([$product_id]);

// Delete product image if exists
if ($product['image'] && file_exists(__DIR__ . '/../../public/assets/images/' . $product['image'])) {
    unlink(__DIR__ . '/../../public/assets/images/' . $product['image']);
}

// Delete product from database
$stmt = $pdo->prepare('DELETE FROM products WHERE id = ?');
$stmt->execute([$product_id]);

// Redirect to dashboard
header('Location: ../dashboard.php');
exit;
?>

9. Managing Categories

a. Creating a New Category (public/categories/create.php)

Create a create.php file inside the public/categories directory to handle category creation.

<?php
// public/categories/create.php

require_once __DIR__ . '/../../config/db.php';
require_once __DIR__ . '/../../templates/header.php';

// Check if user is logged in and has permission to add categories
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to add categories.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $name = trim($_POST['name']);

    if (empty($name)) {
        $errors[] = 'Category name is required.';
    }

    // Check if category already exists
    if (empty($errors)) {
        $stmt = $pdo->prepare('SELECT id FROM categories WHERE name = ?');
        $stmt->execute([$name]);
        if ($stmt->fetch()) {
            $errors[] = 'Category name already exists.';
        }
    }

    if (empty($errors)) {
        // Insert category into database
        $stmt = $pdo->prepare('INSERT INTO categories (name) VALUES (?)');
        if ($stmt->execute([$name])) {
            $success = 'Category added successfully.';
            // Clear form fields
            $name = '';
        } else {
            $errors[] = 'There was an error adding the category.';
        }
    }
}
?>

<h2>Add New Category</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="create.php" method="POST">
    <div class="form-group">
        <label for="name">Category Name:</label>
        <input type="text" id="name" name="name" value="<?php echo htmlspecialchars($name ?? ''); ?>" required>
    </div>
    <button type="submit">Add Category</button>
</form>

<?php
require_once __DIR__ . '/../../templates/footer.php';
?>
b. Editing a Category (public/categories/edit.php)

Create an edit.php file inside the public/categories directory to handle category editing.

<?php
// public/categories/edit.php

require_once __DIR__ . '/../../config/db.php';
require_once __DIR__ . '/../../templates/header.php';

// Check if user is logged in and has permission to edit categories
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to edit categories.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

// Get category ID from URL
$category_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

// Fetch category details
$stmt = $pdo->prepare('SELECT * FROM categories WHERE id = ?');
$stmt->execute([$category_id]);
$category = $stmt->fetch();

if (!$category) {
    echo "<p>Category not found.</p>";
    require_once __DIR__ . '/../../templates/footer.php';
    exit;
}

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $name = trim($_POST['name']);

    if (empty($name)) {
        $errors[] = 'Category name is required.';
    }

    // Check if category name already exists
    if (empty($errors)) {
        $stmt = $pdo->prepare('SELECT id FROM categories WHERE name = ? AND id != ?');
        $stmt->execute([$name, $category_id]);
        if ($stmt->fetch()) {
            $errors[] = 'Category name already exists.';
        }
    }

    if (empty($errors)) {
        // Update category in database
        $stmt = $pdo->prepare('UPDATE categories SET name = ? WHERE id = ?');
        if ($stmt->execute([$name, $category_id])) {
            $success = 'Category updated successfully.';
            // Refresh category data
            $stmt = $pdo->prepare('SELECT * FROM categories WHERE id = ?');
            $stmt->execute([$category_id]);
            $category = $stmt->fetch();
        } else {
            $errors[] = 'There was an error updating the category.';
        }
    }
}
?>

<h2>Edit Category</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<form action="edit.php?id=<?php echo $category_id; ?>" method="POST">
    <div class="form-group">
        <label for="name">Category Name:</label>
        <input type="text" id="name" name="name" value="<?php echo htmlspecialchars($category['name']); ?>" required>
    </div>
    <button type="submit">Update Category</button>
</form>

<?php
require_once __DIR__ . '/../../templates/footer.php';
?>
c. Deleting a Category (public/categories/delete.php)

Create a delete.php file inside the public/categories directory to handle category deletion.

<?php
// public/categories/delete.php

require_once __DIR__ . '/../../config/db.php';

// Check if user is logged in and has permission to delete categories
session_start();
if (!isset($_SESSION['user_id'])) {
    header('Location: ../login.php');
    exit;
}

$user_role = $_SESSION['role'];
if ($user_role !== 'admin' && $user_role !== 'editor') {
    echo "<p>You do not have permission to delete categories.</p>";
    exit;
}

// Get category ID from URL
$category_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

// Fetch category details
$stmt = $pdo->prepare('SELECT * FROM categories WHERE id = ?');
$stmt->execute([$category_id]);
$category = $stmt->fetch();

if (!$category) {
    echo "<p>Category not found.</p>";
    exit;
}

// Delete category from database
$stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
$stmt->execute([$category_id]);

// Redirect to dashboard
header('Location: ../dashboard.php');
exit;
?>

10. Product Listings and Display

a. Displaying Products (public/index.php)

Update the index.php file inside the public directory to display all published products.

<?php
// public/index.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Fetch all published products
$stmt = $pdo->prepare('SELECT products.*, GROUP_CONCAT(categories.name SEPARATOR ", ") AS categories FROM products 
                       JOIN product_categories ON products.id = product_categories.product_id
                       JOIN categories ON categories.id = product_categories.category_id
                       WHERE products.status = ?
                       GROUP BY products.id
                       ORDER BY products.created_at DESC');
$stmt->execute(['published']);
$products = $stmt->fetchAll();
?>

<h2>Available Products</h2>

<div class="product-grid">
    <?php if ($products): ?>
        <?php foreach ($products as $product): ?>
            <div class="product-card">
                <?php if ($product['image']): ?>
                    <img src="/ecommerce/public/assets/images/<?php echo htmlspecialchars($product['image']); ?>" alt="<?php echo htmlspecialchars($product['name']); ?>">
                <?php else: ?>
                    <img src="/ecommerce/public/assets/images/default.png" alt="No Image">
                <?php endif; ?>
                <h3><?php echo htmlspecialchars($product['name']); ?></h3>
                <p><?php echo htmlspecialchars($product['description']); ?></p>
                <p><strong>Price:</strong> $<?php echo htmlspecialchars($product['price']); ?></p>
                <p><strong>Stock:</strong> <?php echo htmlspecialchars($product['stock']); ?></p>
                <p><strong>Categories:</strong> <?php echo htmlspecialchars($product['categories']); ?></p>
                <form action="cart.php" method="POST">
                    <input type="hidden" name="product_id" value="<?php echo $product['id']; ?>">
                    <input type="number" name="quantity" value="1" min="1" max="<?php echo $product['stock']; ?>" required>
                    <button type="submit">Add to Cart</button>
                </form>
            </div>
        <?php endforeach; ?>
    <?php else: ?>
        <p>No products available at the moment.</p>
    <?php endif; ?>
</div>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
b. Styling the Product Grid (public/assets/css/styles.css)

Add the following CSS to style the product listings.

/* public/assets/css/styles.css */

body {
    font-family: Arial, sans-serif;
    margin: 0;
    padding: 0;
}

header, footer {
    background-color: #34495e;
    color: #ecf0f1;
    padding: 10px 20px;
}

header h1, footer p {
    margin: 0;
}

nav a {
    color: #ecf0f1;
    margin-right: 15px;
    text-decoration: none;
}

nav a:hover {
    text-decoration: underline;
}

main {
    padding: 20px;
}

.form-group {
    margin-bottom: 15px;
}

label {
    display: block;
    margin-bottom: 5px;
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="number"],
select,
textarea {
    width: 100%;
    padding: 8px;
    box-sizing: border-box;
}

button {
    padding: 10px 15px;
    background-color: #2980b9;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background-color: #3498db;
}

.error {
    background-color: #f8d7da;
    color: #842029;
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid #f5c2c7;
    border-radius: 4px;
}

.success {
    background-color: #d1e7dd;
    color: #0f5132;
    padding: 15px;
    margin-bottom: 20px;
    border: 1px solid #badbcc;
    border-radius: 4px;
}

.table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
}

.table, .table th, .table td {
    border: 1px solid #bdc3c7;
}

.table th, .table td {
    padding: 12px;
    text-align: left;
}

.table th {
    background-color: #ecf0f1;
}

.action-links a {
    margin-right: 10px;
    color: #2980b9;
    text-decoration: none;
}

.action-links a:hover {
    text-decoration: underline;
}

.product-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}

.product-card {
    border: 1px solid #bdc3c7;
    padding: 15px;
    width: calc(25% - 20px);
    box-sizing: border-box;
    border-radius: 4px;
    background-color: #fff;
}

.product-card img {
    max-width: 100%;
    height: auto;
}

.product-card h3 {
    margin-top: 10px;
}

.product-card p {
    margin: 5px 0;
}

@media (max-width: 768px) {
    .product-card {
        width: calc(50% - 20px);
    }
}

@media (max-width: 480px) {
    .product-card {
        width: 100%;
    }
}

11. Implementing the Shopping Cart

a. Cart Functionality (public/cart.php)

Create a cart.php file inside the public directory to handle shopping cart operations.

<?php
// public/cart.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Initialize cart if not set
if (!isset($_SESSION['cart'])) {
    $_SESSION['cart'] = [];
}

$errors = [];
$success = '';

// Handle adding items to cart
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $product_id = (int)$_POST['product_id'];
    $quantity = (int)$_POST['quantity'];

    // Fetch product details
    $stmt = $pdo->prepare('SELECT * FROM products WHERE id = ? AND status = ?');
    $stmt->execute([$product_id, 'published']);
    $product = $stmt->fetch();

    if ($product) {
        if ($quantity > $product['stock']) {
            $errors[] = 'Requested quantity exceeds available stock.';
        } else {
            // Add to cart
            if (isset($_SESSION['cart'][$product_id])) {
                $_SESSION['cart'][$product_id] += $quantity;
            } else {
                $_SESSION['cart'][$product_id] = $quantity;
            }
            $success = 'Product added to cart successfully.';
        }
    } else {
        $errors[] = 'Product not found.';
    }
}

// Handle updating cart quantities
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_cart'])) {
    foreach ($_POST['quantities'] as $product_id => $quantity) {
        $product_id = (int)$product_id;
        $quantity = (int)$quantity;

        // Fetch product details
        $stmt = $pdo->prepare('SELECT stock FROM products WHERE id = ? AND status = ?');
        $stmt->execute([$product_id, 'published']);
        $product = $stmt->fetch();

        if ($product) {
            if ($quantity > $product['stock']) {
                $errors[] = 'Requested quantity for product ID ' . $product_id . ' exceeds available stock.';
            } elseif ($quantity < 1) {
                unset($_SESSION['cart'][$product_id]);
            } else {
                $_SESSION['cart'][$product_id] = $quantity;
            }
        } else {
            unset($_SESSION['cart'][$product_id]);
        }
    }
    if (empty($errors)) {
        $success = 'Cart updated successfully.';
    }
}

// Handle removing items from cart
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['remove_item'])) {
    $remove_id = (int)$_POST['remove_id'];
    unset($_SESSION['cart'][$remove_id]);
    $success = 'Item removed from cart.';
}

// Fetch cart items
$cart_items = [];
$total = 0.00;

if (!empty($_SESSION['cart'])) {
    $placeholders = implode(',', array_fill(0, count($_SESSION['cart']), '?'));
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id IN ($placeholders) AND status = ?");
    $stmt->execute(array_merge(array_keys($_SESSION['cart']), ['published']));
    $products = $stmt->fetchAll();

    foreach ($products as $product) {
        $quantity = $_SESSION['cart'][$product['id']];
        $subtotal = $product['price'] * $quantity;
        $total += $subtotal;
        $cart_items[] = [
            'id' => $product['id'],
            'name' => $product['name'],
            'price' => $product['price'],
            'quantity' => $quantity,
            'stock' => $product['stock'],
            'subtotal' => $subtotal,
        ];
    }
}
?>

<h2>Your Shopping Cart</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<?php if ($cart_items): ?>
    <form action="cart.php" method="POST">
        <table class="table">
            <thead>
                <tr>
                    <th>Name</th>
                    <th>Price ($)</th>
                    <th>Quantity</th>
                    <th>Subtotal ($)</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($cart_items as $item): ?>
                    <tr>
                        <td><?php echo htmlspecialchars($item['name']); ?></td>
                        <td><?php echo htmlspecialchars($item['price']); ?></td>
                        <td>
                            <input type="number" name="quantities[<?php echo $item['id']; ?>]" value="<?php echo $item['quantity']; ?>" min="1" max="<?php echo $item['stock']; ?>" required>
                        </td>
                        <td><?php echo htmlspecialchars(number_format($item['subtotal'], 2)); ?></td>
                        <td>
                            <button type="submit" name="remove_item" value="Remove">Remove</button>
                            <input type="hidden" name="remove_id" value="<?php echo $item['id']; ?>">
                        </td>
                    </tr>
                <?php endforeach; ?>
                <tr>
                    <td colspan="3" style="text-align:right;"><strong>Total:</strong></td>
                    <td colspan="2"><strong>$<?php echo number_format($total, 2); ?></strong></td>
                </tr>
            </tbody>
        </table>
        <button type="submit" name="update_cart">Update Cart</button>
    </form>
    <a href="checkout.php">Proceed to Checkout</a>
<?php else: ?>
    <p>Your cart is empty.</p>
<?php endif; ?>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
b. Styling the Cart (public/assets/css/styles.css)

Add the following CSS to style the cart and product grid.

/* public/assets/css/styles.css */

/* Existing styles... */

/* Additional styles for the cart */

.product-grid {
    display: flex;
    flex-wrap: wrap;
    gap: 20px;
}

.product-card {
    border: 1px solid #bdc3c7;
    padding: 15px;
    width: calc(25% - 20px);
    box-sizing: border-box;
    border-radius: 4px;
    background-color: #fff;
}

.product-card img {
    max-width: 100%;
    height: auto;
}

.product-card h3 {
    margin-top: 10px;
}

.product-card p {
    margin: 5px 0;
}

.product-card form {
    margin-top: 10px;
}

.cart-table {
    width: 100%;
    border-collapse: collapse;
}

.cart-table th, .cart-table td {
    padding: 10px;
    border: 1px solid #bdc3c7;
}

.cart-table th {
    background-color: #ecf0f1;
}

.action-links a, .action-links button {
    margin-right: 10px;
    color: #2980b9;
    text-decoration: none;
    background: none;
    border: none;
    cursor: pointer;
    padding: 0;
    font: inherit;
}

.action-links a:hover, .action-links button:hover {
    text-decoration: underline;
}

@media (max-width: 768px) {
    .product-card {
        width: calc(50% - 20px);
    }
}

@media (max-width: 480px) {
    .product-card {
        width: 100%;
    }
}

12. Implementing the Checkout Process

a. Checkout Page (public/checkout.php)

Create a checkout.php file inside the public directory to handle order processing.

<?php
// public/checkout.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

// Fetch cart items
$cart_items = [];
$total = 0.00;

if (!empty($_SESSION['cart'])) {
    $placeholders = implode(',', array_fill(0, count($_SESSION['cart']), '?'));
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id IN ($placeholders) AND status = ?");
    $stmt->execute(array_merge(array_keys($_SESSION['cart']), ['published']));
    $products = $stmt->fetchAll();

    foreach ($products as $product) {
        $quantity = $_SESSION['cart'][$product['id']];
        $subtotal = $product['price'] * $quantity;
        $total += $subtotal;
        $cart_items[] = [
            'id' => $product['id'],
            'name' => $product['name'],
            'price' => $product['price'],
            'quantity' => $quantity,
            'subtotal' => $subtotal,
        ];
    }
}

$errors = [];
$success = '';

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (empty($cart_items)) {
        $errors[] = 'Your cart is empty.';
    } else {
        // Begin transaction
        $pdo->beginTransaction();
        try {
            // Create order
            $stmt = $pdo->prepare('INSERT INTO orders (user_id, total_amount, status) VALUES (?, ?, ?)');
            $stmt->execute([$_SESSION['user_id'], $total, 'pending']);
            $order_id = $pdo->lastInsertId();

            // Insert order items and update product stock
            $stmt_order_item = $pdo->prepare('INSERT INTO order_items (order_id, product_id, quantity, price) VALUES (?, ?, ?, ?)');
            $stmt_update_stock = $pdo->prepare('UPDATE products SET stock = stock - ? WHERE id = ?');

            foreach ($cart_items as $item) {
                $stmt_order_item->execute([$order_id, $item['id'], $item['quantity'], $item['price']]);
                $stmt_update_stock->execute([$item['quantity'], $item['id']]);
            }

            // Commit transaction
            $pdo->commit();

            // Clear cart
            $_SESSION['cart'] = [];

            $success = 'Your order has been placed successfully.';
        } catch (Exception $e) {
            // Rollback transaction
            $pdo->rollBack();
            $errors[] = 'There was an error processing your order. Please try again.';
        }
    }
}
?>

<h2>Checkout</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<?php if ($cart_items): ?>
    <h3>Order Summary</h3>
    <table class="table">
        <thead>
            <tr>
                <th>Name</th>
                <th>Price ($)</th>
                <th>Quantity</th>
                <th>Subtotal ($)</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($cart_items as $item): ?>
                <tr>
                    <td><?php echo htmlspecialchars($item['name']); ?></td>
                    <td><?php echo htmlspecialchars($item['price']); ?></td>
                    <td><?php echo htmlspecialchars($item['quantity']); ?></td>
                    <td><?php echo htmlspecialchars(number_format($item['subtotal'], 2)); ?></td>
                </tr>
            <?php endforeach; ?>
            <tr>
                <td colspan="3" style="text-align:right;"><strong>Total:</strong></td>
                <td><strong>$<?php echo number_format($total, 2); ?></strong></td>
            </tr>
        </tbody>
    </table>

    <form action="checkout.php" method="POST">
        <button type="submit">Confirm Order</button>
    </form>
<?php else: ?>
    <p>Your cart is empty.</p>
<?php endif; ?>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>

13. Managing Orders

a. Viewing Orders (public/orders.php)

Create an orders.php file inside the public directory to allow users and admins to view orders.

<?php
// public/orders.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

$user_role = $_SESSION['role'];

// Fetch orders
if ($user_role === 'admin') {
    // Admin can see all orders
    $stmt = $pdo->query('SELECT orders.*, users.username FROM orders JOIN users ON orders.user_id = users.id ORDER BY orders.created_at DESC');
} else {
    // Customers can see their own orders
    $stmt = $pdo->prepare('SELECT * FROM orders WHERE user_id = ? ORDER BY created_at DESC');
    $stmt->execute([$_SESSION['user_id']]);
}
$orders = $stmt->fetchAll();
?>

<h2>Your Orders</h2>

<?php if ($orders): ?>
    <table class="table">
        <thead>
            <tr>
                <th>Order ID</th>
                <?php if ($user_role === 'admin'): ?>
                    <th>Customer</th>
                <?php endif; ?>
                <th>Total Amount ($)</th>
                <th>Status</th>
                <th>Created At</th>
                <th>Actions</th>
            </tr>
        </thead>
        <tbody>
            <?php foreach ($orders as $order): ?>
                <tr>
                    <td><?php echo htmlspecialchars($order['id']); ?></td>
                    <?php if ($user_role === 'admin'): ?>
                        <td><?php echo htmlspecialchars($order['username']); ?></td>
                    <?php endif; ?>
                    <td><?php echo htmlspecialchars($order['total_amount']); ?></td>
                    <td><?php echo htmlspecialchars($order['status']); ?></td>
                    <td><?php echo htmlspecialchars($order['created_at']); ?></td>
                    <td>
                        <a href="view_order.php?id=<?php echo $order['id']; ?>">View</a>
                        <?php if ($user_role === 'admin'): ?>
                            <?php if ($order['status'] === 'pending'): ?>
                                <a href="update_order.php?id=<?php echo $order['id']; ?>&status=completed">Mark as Completed</a>
                                <a href="update_order.php?id=<?php echo $order['id']; ?>&status=cancelled">Cancel Order</a>
                            <?php endif; ?>
                        <?php endif; ?>
                    </td>
                </tr>
            <?php endforeach; ?>
        </tbody>
    </table>
<?php else: ?>
    <p>No orders found.</p>
<?php endif; ?>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
b. Viewing a Single Order (public/view_order.php)

Create a view_order.php file inside the public directory to display order details.

<?php
// public/view_order.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

$user_role = $_SESSION['role'];

// Get order ID from URL
$order_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;

// Fetch order details
if ($user_role === 'admin') {
    $stmt = $pdo->prepare('SELECT orders.*, users.username FROM orders JOIN users ON orders.user_id = users.id WHERE orders.id = ?');
    $stmt->execute([$order_id]);
} else {
    $stmt = $pdo->prepare('SELECT * FROM orders WHERE id = ? AND user_id = ?');
    $stmt->execute([$order_id, $_SESSION['user_id']]);
}
$order = $stmt->fetch();

if (!$order) {
    echo "<p>Order not found.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

// Fetch order items
$stmt = $pdo->prepare('SELECT order_items.*, products.name FROM order_items JOIN products ON order_items.product_id = products.id WHERE order_items.order_id = ?');
$stmt->execute([$order_id]);
$order_items = $stmt->fetchAll();
?>

<h2>Order Details</h2>

<table class="table">
    <tr>
        <th>Order ID:</th>
        <td><?php echo htmlspecialchars($order['id']); ?></td>
    </tr>
    <?php if ($user_role === 'admin'): ?>
        <tr>
            <th>Customer:</th>
            <td><?php echo htmlspecialchars($order['username']); ?></td>
        </tr>
    <?php endif; ?>
    <tr>
        <th>Total Amount ($):</th>
        <td><?php echo htmlspecialchars($order['total_amount']); ?></td>
    </tr>
    <tr>
        <th>Status:</th>
        <td><?php echo htmlspecialchars($order['status']); ?></td>
    </tr>
    <tr>
        <th>Created At:</th>
        <td><?php echo htmlspecialchars($order['created_at']); ?></td>
    </tr>
</table>

<h3>Order Items</h3>
<table class="table">
    <thead>
        <tr>
            <th>Product Name</th>
            <th>Price ($)</th>
            <th>Quantity</th>
            <th>Subtotal ($)</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($order_items as $item): ?>
            <tr>
                <td><?php echo htmlspecialchars($item['name']); ?></td>
                <td><?php echo htmlspecialchars($item['price']); ?></td>
                <td><?php echo htmlspecialchars($item['quantity']); ?></td>
                <td><?php echo htmlspecialchars(number_format($item['price'] * $item['quantity'], 2)); ?></td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

<?php if ($user_role === 'admin'): ?>
    <h3>Update Order Status</h3>
    <form action="update_order.php" method="GET">
        <input type="hidden" name="id" value="<?php echo $order['id']; ?>">
        <label for="status">Change Status:</label>
        <select id="status" name="status">
            <option value="pending" <?php if ($order['status'] === 'pending') echo 'selected'; ?>>Pending</option>
            <option value="completed" <?php if ($order['status'] === 'completed') echo 'selected'; ?>>Completed</option>
            <option value="cancelled" <?php if ($order['status'] === 'cancelled') echo 'selected'; ?>>Cancelled</option>
        </select>
        <button type="submit">Update Status</button>
    </form>
<?php endif; ?>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
c. Updating Order Status (public/update_order.php)

Create an update_order.php file inside the public directory to handle updating order statuses.

<?php
// public/update_order.php

require_once __DIR__ . '/../config/db.php';

// Check if user is logged in and is admin
session_start();
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
    echo "<p>You do not have permission to perform this action.</p>";
    exit;
}

// Get order ID and new status from URL
$order_id = isset($_GET['id']) ? (int)$_GET['id'] : 0;
$new_status = isset($_GET['status']) ? $_GET['status'] : '';

$valid_statuses = ['pending', 'completed', 'cancelled'];

if (!in_array($new_status, $valid_statuses)) {
    echo "<p>Invalid status selected.</p>";
    exit;
}

// Update order status
$stmt = $pdo->prepare('UPDATE orders SET status = ? WHERE id = ?');
if ($stmt->execute([$new_status, $order_id])) {
    header('Location: view_order.php?id=' . $order_id);
    exit;
} else {
    echo "<p>There was an error updating the order status.</p>";
    exit;
}
?>

14. Managing Users (Admin Only)

a. Managing Users (public/manage_users.php)

Create a manage_users.php file inside the public directory to allow admins to manage users.

<?php
// public/manage_users.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../templates/header.php';

// Check if user is logged in and is admin
if (!isset($_SESSION['user_id']) || $_SESSION['role'] !== 'admin') {
    echo "<p>You do not have permission to access this page.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

$errors = [];
$success = '';

// Handle user role updates
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['update_role'])) {
    $user_id = (int)$_POST['user_id'];
    $new_role = $_POST['role'];

    $valid_roles = ['customer', 'admin'];

    if (!in_array($new_role, $valid_roles)) {
        $errors[] = 'Invalid role selected.';
    }

    if (empty($errors)) {
        $stmt = $pdo->prepare('UPDATE users SET role = ? WHERE id = ?');
        if ($stmt->execute([$new_role, $user_id])) {
            $success = 'User role updated successfully.';
        } else {
            $errors[] = 'There was an error updating the user role.';
        }
    }
}

// Fetch all users
$stmt = $pdo->query('SELECT * FROM users');
$users = $stmt->fetchAll();
?>

<h2>Manage Users</h2>

<?php if (!empty($errors)): ?>
    <div class="error">
        <ul>
            <?php foreach($errors as $error): ?>
                <li><?php echo htmlspecialchars($error); ?></li>
            <?php endforeach; ?>
        </ul>
    </div>
<?php endif; ?>

<?php if ($success): ?>
    <div class="success">
        <p><?php echo htmlspecialchars($success); ?></p>
    </div>
<?php endif; ?>

<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>Username</th>
            <th>Email</th>
            <th>Role</th>
            <th>Created At</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($users as $user): ?>
            <tr>
                <td><?php echo htmlspecialchars($user['id']); ?></td>
                <td><?php echo htmlspecialchars($user['username']); ?></td>
                <td><?php echo htmlspecialchars($user['email']); ?></td>
                <td><?php echo htmlspecialchars($user['role']); ?></td>
                <td><?php echo htmlspecialchars($user['created_at']); ?></td>
                <td>
                    <form action="manage_users.php" method="POST" style="display:inline;">
                        <input type="hidden" name="user_id" value="<?php echo $user['id']; ?>">
                        <select name="role" required>
                            <option value="customer" <?php if ($user['role'] === 'customer') echo 'selected'; ?>>Customer</option>
                            <option value="admin" <?php if ($user['role'] === 'admin') echo 'selected'; ?>>Admin</option>
                        </select>
                        <button type="submit" name="update_role">Update Role</button>
                    </form>
                </td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>

15. Implementing Payment Processing

For simplicity, we’ll integrate PayPal as the payment gateway. You can replace this with other payment gateways as needed.

a. Setting Up PayPal Integration
  1. Create a PayPal Developer Account:
  2. Obtain API Credentials:
    • Navigate to the Dashboard.
    • Under My Apps & Credentials, create a new app to obtain the Client ID and Secret.
  3. Install PayPal PHP SDK via Composer:
    • Navigate to your project root directory.
    • Run the following command: composer require paypal/rest-api-sdk-php
    • Note: PayPal has deprecated the REST API SDK. It’s recommended to use the latest SDKs or APIs provided by PayPal. For the sake of this example, we’ll proceed with the deprecated SDK for simplicity.
b. Creating the Payment Script (public/payment.php)

Create a payment.php file inside the public directory to handle payment processing.

<?php
// public/payment.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../vendor/autoload.php';

// PayPal API context
use PayPal\Rest\ApiContext;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Api\Payer;
use PayPal\Api\Item;
use PayPal\Api\ItemList;
use PayPal\Api\Amount;
use PayPal\Api\Transaction;
use PayPal\Api\RedirectUrls;
use PayPal\Api\Payment;

// Start session
session_start();

// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

// Fetch cart items
$cart_items = [];
$total = 0.00;

if (!empty($_SESSION['cart'])) {
    $placeholders = implode(',', array_fill(0, count($_SESSION['cart']), '?'));
    $stmt = $pdo->prepare("SELECT * FROM products WHERE id IN ($placeholders) AND status = ?");
    $stmt->execute(array_merge(array_keys($_SESSION['cart']), ['published']));
    $products = $stmt->fetchAll();

    foreach ($products as $product) {
        $quantity = $_SESSION['cart'][$product['id']];
        $subtotal = $product['price'] * $quantity;
        $total += $subtotal;
        $cart_items[] = [
            'id' => $product['id'],
            'name' => $product['name'],
            'price' => $product['price'],
            'quantity' => $quantity,
            'subtotal' => $subtotal,
        ];
    }
}

// PayPal configuration
$paypal_conf = [
    'client_id' => 'YOUR_PAYPAL_CLIENT_ID',
    'secret' => 'YOUR_PAYPAL_SECRET',
    'settings' => [
        'mode' => 'sandbox', // 'live' for production
        'http.ConnectionTimeOut' => 30,
        'log.LogEnabled' => true,
        'log.FileName' => '../PayPal.log',
        'log.LogLevel' => 'FINE'
    ],
];

// Get API context
$apiContext = new ApiContext(
    new OAuthTokenCredential(
        $paypal_conf['client_id'],
        $paypal_conf['secret']
    )
);
$apiContext->setConfig($paypal_conf['settings']);

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    if (empty($cart_items)) {
        echo "<p>Your cart is empty.</p>";
        exit;
    }

    // Create new payer and method
    $payer = new Payer();
    $payer->setPaymentMethod("paypal");

    // Items
    $items = [];
    foreach ($cart_items as $item_data) {
        $item = new Item();
        $item->setName($item_data['name'])
             ->setCurrency('USD')
             ->setQuantity($item_data['quantity'])
             ->setPrice($item_data['price']);
        $items[] = $item;
    }

    $itemList = new ItemList();
    $itemList->setItems($items);

    // Amount
    $amount = new Amount();
    $amount->setCurrency("USD")
           ->setTotal(number_format($total, 2, '.', ''));

    // Transaction
    $transaction = new Transaction();
    $transaction->setAmount($amount)
                ->setItemList($itemList)
                ->setDescription("Purchase from My E-commerce Store")
                ->setInvoiceNumber(uniqid());

    // Redirect URLs
    $redirectUrls = new RedirectUrls();
    $redirectUrls->setReturnUrl("http://localhost/ecommerce/public/execute_payment.php?success=true")
                 ->setCancelUrl("http://localhost/ecommerce/public/execute_payment.php?success=false");

    // Payment
    $payment = new Payment();
    $payment->setIntent("sale")
            ->setPayer($payer)
            ->setRedirectUrls($redirectUrls)
            ->setTransactions([$transaction]);

    try {
        $payment->create($apiContext);
    } catch (Exception $ex) {
        echo "<p>Sorry, something went wrong. Please try again.</p>";
        exit;
    }

    // Get PayPal redirect URL and redirect the customer
    foreach ($payment->getLinks() as $link) {
        if ($link->getRel() === 'approval_url') {
            $redirect_url = $link->getHref();
            break;
        }
    }

    // Store payment ID in session
    $_SESSION['paypal_payment_id'] = $payment->getId();

    if (isset($redirect_url)) {
        // Redirect to PayPal
        header("Location: {$redirect_url}");
        exit;
    }

    echo "<p>Unknown error occurred.</p>";
    exit;
}
?>

<h2>Checkout</h2>

<?php if ($cart_items): ?>
    <form action="payment.php" method="POST">
        <h3>Order Summary</h3>
        <table class="table">
            <thead>
                <tr>
                    <th>Product Name</th>
                    <th>Price ($)</th>
                    <th>Quantity</th>
                    <th>Subtotal ($)</th>
                </tr>
            </thead>
            <tbody>
                <?php foreach ($cart_items as $item): ?>
                    <tr>
                        <td><?php echo htmlspecialchars($item['name']); ?></td>
                        <td><?php echo htmlspecialchars($item['price']); ?></td>
                        <td><?php echo htmlspecialchars($item['quantity']); ?></td>
                        <td><?php echo htmlspecialchars(number_format($item['subtotal'], 2)); ?></td>
                    </tr>
                <?php endforeach; ?>
                <tr>
                    <td colspan="3" style="text-align:right;"><strong>Total:</strong></td>
                    <td><strong>$<?php echo number_format($total, 2); ?></strong></td>
                </tr>
            </tbody>
        </table>
        <button type="submit">Proceed to PayPal</button>
    </form>
<?php else: ?>
    <p>Your cart is empty.</p>
<?php endif; ?>

<?php
require_once __DIR__ . '/../templates/footer.php';
?>
b. Executing Payment (public/execute_payment.php)

Create an execute_payment.php file inside the public directory to handle payment execution.

<?php
// public/execute_payment.php

require_once __DIR__ . '/../config/db.php';
require_once __DIR__ . '/../vendor/autoload.php';

// PayPal API context
use PayPal\Rest\ApiContext;
use PayPal\Auth\OAuthTokenCredential;
use PayPal\Api\Payment;
use PayPal\Api\PaymentExecution;

// Start session
session_start();

// PayPal configuration
$paypal_conf = [
    'client_id' => 'YOUR_PAYPAL_CLIENT_ID',
    'secret' => 'YOUR_PAYPAL_SECRET',
    'settings' => [
        'mode' => 'sandbox', // 'live' for production
        'http.ConnectionTimeOut' => 30,
        'log.LogEnabled' => true,
        'log.FileName' => '../PayPal.log',
        'log.LogLevel' => 'FINE'
    ],
];

// Get API context
$apiContext = new ApiContext(
    new OAuthTokenCredential(
        $paypal_conf['client_id'],
        $paypal_conf['secret']
    )
);
$apiContext->setConfig($paypal_conf['settings']);

// Get payment ID and payer ID from query parameters
$payment_id = isset($_GET['paymentId']) ? $_GET['paymentId'] : '';
$payer_id = isset($_GET['PayerID']) ? $_GET['PayerID'] : '';
$success = isset($_GET['success']) ? $_GET['success'] : 'false';

if ($success === 'true' && $payment_id && $payer_id) {
    // Execute payment
    $payment = Payment::get($payment_id, $apiContext);
    $execution = new PaymentExecution();
    $execution->setPayerId($payer_id);

    try {
        // Execute the payment
        $result = $payment->execute($execution, $apiContext);

        // Check if payment was successful
        if ($result->getState() === 'approved') {
            echo "<p>Payment successful! Thank you for your purchase.</p>";
            // Optionally, update order status in the database here
        } else {
            echo "<p>Payment failed.</p>";
        }
    } catch (Exception $ex) {
        echo "<p>Payment execution failed. Please try again.</p>";
    }
} else {
    echo "<p>Payment was canceled.</p>";
}
?>

16. Testing the E-commerce Platform

  1. Set Up Categories:
    • Navigate to http://localhost/ecommerce/public/categories/create.php.
    • Add several categories to organize products.
  2. Add Products:
    • Navigate to http://localhost/ecommerce/public/products/create.php.
    • Add new products by filling out the form, uploading images, and assigning categories.
  3. View Products:
    • Navigate to http://localhost/ecommerce/public/index.php to view all published products.
    • Add products to the cart and proceed to checkout.
  4. Handle Cart Operations:
    • Add multiple products to the cart.
    • Update quantities or remove items from the cart.
  5. Checkout and Payment:
    • Proceed to checkout and initiate payment via PayPal.
    • Complete the payment process in the PayPal sandbox environment.
    • Verify that orders are recorded correctly in the database.
  6. Manage Orders (Admin):
    • Log in as an admin and navigate to http://localhost/ecommerce/public/orders.php.
    • View all orders, update order statuses, and manage customer information.
  7. Manage Users (Admin):
    • Navigate to http://localhost/ecommerce/public/manage_users.php.
    • Update user roles and manage user accounts.
  8. Security Testing:
    • Attempt unauthorized access to admin functionalities.
    • Test input validations by submitting invalid or malicious data.

17. Deployment Considerations

When deploying your E-commerce Platform to a live server, consider the following:

  • Secure Authentication:
    • Implement HTTPS to encrypt data transmission.
    • Enforce strong password policies and consider implementing multi-factor authentication.
  • Input Validation and Sanitization:
    • Thoroughly validate and sanitize all user inputs to prevent SQL injection, XSS, and other attacks.
  • Payment Gateway Security:
    • Use live credentials for payment gateways and ensure secure handling of payment data.
    • Comply with PCI DSS standards for handling payment information.
  • Session Management:
    • Use secure session cookies (session.cookie_secure and session.cookie_httponly) to protect session data.
  • Error Handling:
    • Provide user-friendly error messages without exposing sensitive information.
    • Implement logging mechanisms to monitor and troubleshoot issues.
  • Performance Optimization:
    • Optimize database queries and use caching strategies to enhance performance.
    • Use Content Delivery Networks (CDNs) for serving static assets.
  • Scalability:
    • Design the platform to handle increased traffic and data volume as your business grows.
  • Backup and Recovery:
    • Implement regular backups of your database and application files to prevent data loss.
  • SEO Optimization:
    • Implement SEO best practices, including clean URLs, meta tags, and sitemaps to improve search engine visibility.
  • Legal Compliance:
    • Ensure compliance with relevant laws and regulations, such as GDPR for data protection.

18. Enhancements and Best Practices

  • Responsive Design:
    • Ensure the platform is mobile-friendly and provides a seamless experience across devices.
  • User Reviews and Ratings:
    • Allow customers to leave reviews and ratings for products to build trust and provide feedback.
  • Inventory Management:
    • Implement advanced inventory management features to track stock levels, alerts for low stock, and automated restocking.
  • Discounts and Promotions:
    • Add functionalities for discount codes, promotions, and special offers to attract customers.
  • Wishlist Functionality:
    • Allow users to add products to a wishlist for future purchases.
  • Advanced Search and Filtering:
    • Implement search functionalities with filters based on categories, price ranges, and other product attributes.
  • Email Notifications:
    • Send confirmation emails upon registration, order placement, and status updates.
  • User Profile Management:
    • Allow users to manage their profiles, view order history, and update personal information.
  • Analytics Integration:
    • Integrate tools like Google Analytics to monitor website traffic and user behavior.
  • Use of PHP Frameworks:
    • Consider using frameworks like Laravel or Symfony to leverage built-in features, enhance security, and streamline development.
  • Testing:
    • Implement unit and integration tests to ensure the reliability and correctness of the platform.
  • Security Enhancements:
    • Implement CSRF tokens in forms to protect against Cross-Site Request Forgery attacks.
    • Use prepared statements consistently to prevent SQL injection.

Leave a Comment

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

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