Project Overview

A Content Management System (CMS) allows users to create, manage, and modify content on a website without needing specialized technical knowledge. This project covers building a simple CMS using PHP and MySQL, enabling functionalities like user authentication, content creation, editing, deletion, and displaying content dynamically. A CMS is fundamental for websites, blogs, e-commerce platforms, and more.

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 (optional)
  • Basic Understanding of MVC Architecture, Sessions, and CRUD Operations in PHP

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 (Optional):

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 cms.
    • 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(‘admin’,’editor’,’author’) NO ‘author’ created_at TIMESTAMP NO CURRENT_TIMESTAMP
    • posts Table: Field Type Null Key Default Extra id INT NO PRI NULL AUTO_INCREMENT user_id INT NO MUL NULL title VARCHAR(255) NO NULL content TEXT NO NULL status ENUM(‘draft’,’published’) NO ‘draft’ created_at TIMESTAMP NO CURRENT_TIMESTAMP updated_at TIMESTAMP YES NULL ON UPDATE 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
    • post_categories Table: (Pivot table for many-to-many relationship) Field Type Null Key Default Extra post_id INT NO MUL NULL category_id INT NO MUL NULL
      • Composite Primary Key: (post_id, category_id)
  4. Establish Foreign Keys:
    • posts.user_id references users.id with ON DELETE CASCADE.
    • post_categories.post_id references posts.id with ON DELETE CASCADE.
    • post_categories.category_id references categories.id with ON DELETE CASCADE.

3. Project Structure

Organize your project files as follows:

cms/
├── vendor/             # Composer dependencies (if any)
├── config/
│   └── db.php
├── public/
│   ├── index.php
│   ├── login.php
│   ├── register.php
│   ├── logout.php
│   ├── dashboard.php
│   ├── create_post.php
│   ├── edit_post.php
│   ├── delete_post.php
│   ├── view_post.php
│   ├── manage_categories.php
│   ├── assets/
│   │   ├── css/
│   │   │   └── styles.css
│   │   └── js/
│   │       └── scripts.js
├── src/
│   ├── Controller.php
│   ├── UserController.php
│   ├── PostController.php
│   ├── CategoryController.php
│   └── models/
│       ├── User.php
│       ├── Post.php
│       └── Category.php
├── templates/
│   ├── header.php
│   └── footer.php
├── .htaccess
├── composer.json
├── README.md

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   = 'cms';
$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]

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 CMS</title>
    <link rel="stylesheet" href="/cms/public/assets/css/styles.css">
</head>
<body>
    <header>
        <h1>My Content Management System</h1>
        <nav>
            <?php if (isset($_SESSION['user_id'])): ?>
                <a href="/cms/public/dashboard.php">Dashboard</a>
                <a href="/cms/public/create_post.php">Create Post</a>
                <a href="/cms/public/manage_categories.php">Manage Categories</a>
                <a href="/cms/public/logout.php">Logout (<?php echo htmlspecialchars($_SESSION['username']); ?>)</a>
            <?php else: ?>
                <a href="/cms/public/register.php">Register</a>
                <a href="/cms/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 CMS</p>
    </footer>
    <script src="/cms/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']);
    $role     = 'author'; // Default role

    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, $role])) {
            $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 content.

<?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 the CMS.</p>
    <ul>
        <li><a href="create_post.php">Create New Post</a></li>
        <li><a href="manage_categories.php">Manage Categories</a></li>
        <li><a href="manage_users.php">Manage Users</a></li>
    </ul>
<?php elseif ($user_role === 'editor'): ?>
    <p>Welcome, Editor! You can create and edit posts.</p>
    <ul>
        <li><a href="create_post.php">Create New Post</a></li>
        <li><a href="manage_categories.php">Manage Categories</a></li>
    </ul>
<?php else: ?>
    <p>Welcome, Author! You can create and manage your own posts.</p>
    <ul>
        <li><a href="create_post.php">Create New Post</a></li>
    </ul>
<?php endif; ?>

<h3>Your Posts</h3>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>Title</th>
            <th>Status</th>
            <th>Created At</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
    <?php
    if ($user_role === 'admin') {
        // Admin can see all posts
        $stmt = $pdo->query('SELECT posts.id, posts.title, posts.status, posts.created_at, users.username FROM posts JOIN users ON posts.user_id = users.id ORDER BY posts.created_at DESC');
    } else {
        // Other roles see only their own posts
        $stmt = $pdo->prepare('SELECT id, title, status, created_at FROM posts WHERE user_id = ? ORDER BY created_at DESC');
        $stmt->execute([$_SESSION['user_id']]);
    }
    $posts = $stmt->fetchAll();

    if ($posts):
        foreach ($posts as $post):
    ?>
            <tr>
                <td><?php echo htmlspecialchars($post['id']); ?></td>
                <td><?php echo htmlspecialchars($post['title']); ?></td>
                <td><?php echo htmlspecialchars($post['status']); ?></td>
                <td><?php echo htmlspecialchars($post['created_at']); ?></td>
                <td class="action-links">
                    <a href="view_post.php?id=<?php echo $post['id']; ?>">View</a>
                    <a href="edit_post.php?id=<?php echo $post['id']; ?>">Edit</a>
                    <a href="delete_post.php?id=<?php echo $post['id']; ?>" onclick="return confirm('Are you sure you want to delete this post?');">Delete</a>
                </td>
            </tr>
    <?php
        endforeach;
    else:
        echo '<tr><td colspan="5">No posts found.</td></tr>';
    endif;
    ?>
    </tbody>
</table>

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

8. Creating a New Post (public/create_post.php)

Create a create_post.php file inside the public directory to handle post creation.

<?php
// public/create_post.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'];
if ($user_role !== 'admin' && $user_role !== 'editor' && $user_role !== 'author') {
    echo "<p>You do not have permission to create posts.</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
    $title = trim($_POST['title']);
    $content = trim($_POST['content']);
    $status = $_POST['status'] ?? 'draft';
    $selected_categories = $_POST['categories'] ?? [];

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

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

    if (!in_array($status, ['draft', 'published'])) {
        $errors[] = 'Invalid post status.';
    }

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

    if (empty($errors)) {
        // Insert post into database
        $stmt = $pdo->prepare('INSERT INTO posts (user_id, title, content, status) VALUES (?, ?, ?, ?)');
        if ($stmt->execute([$_SESSION['user_id'], $title, $content, $status])) {
            $post_id = $pdo->lastInsertId();
            // Assign categories to post
            $stmt = $pdo->prepare('INSERT INTO post_categories (post_id, category_id) VALUES (?, ?)');
            foreach ($selected_categories as $category_id) {
                $stmt->execute([$post_id, $category_id]);
            }
            $success = 'Post created successfully.';
            // Clear form fields
            $title = $content = '';
            $selected_categories = [];
        } else {
            $errors[] = 'There was an error creating the post.';
        }
    }
}
?>

<h2>Create New Post</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_post.php" method="POST">
    <div class="form-group">
        <label for="title">Post Title:</label>
        <input type="text" id="title" name="title" value="<?php echo htmlspecialchars($title ?? ''); ?>" required>
    </div>

    <div class="form-group">
        <label for="content">Post Content:</label>
        <textarea id="content" name="content" rows="10" required><?php echo htmlspecialchars($content ?? ''); ?></textarea>
    </div>

    <div class="form-group">
        <label for="status">Post Status:</label>
        <select id="status" name="status">
            <option value="draft" <?php if (($status ?? '') === 'draft') echo 'selected'; ?>>Draft</option>
            <option value="published" <?php if (($status ?? '') === 'published') echo 'selected'; ?>>Published</option>
        </select>
    </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">Create Post</button>
</form>

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

9. Editing a Post (public/edit_post.php)

Create an edit_post.php file inside the public directory to handle post editing.

<?php
// public/edit_post.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'];
if ($user_role !== 'admin' && $user_role !== 'editor' && $user_role !== 'author') {
    echo "<p>You do not have permission to edit posts.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

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

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

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

// For Authors, ensure they can only edit their own posts
if ($user_role === 'author' && $post['user_id'] !== $_SESSION['user_id']) {
    echo "<p>You do not have permission to edit this post.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

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

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

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

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Sanitize and validate inputs
    $title = trim($_POST['title']);
    $content = trim($_POST['content']);
    $status = $_POST['status'] ?? 'draft';
    $selected_categories = $_POST['categories'] ?? [];

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

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

    if (!in_array($status, ['draft', 'published'])) {
        $errors[] = 'Invalid post status.';
    }

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

    if (empty($errors)) {
        // Update post in database
        $stmt = $pdo->prepare('UPDATE posts SET title = ?, content = ?, status = ? WHERE id = ?');
        if ($stmt->execute([$title, $content, $status, $post_id])) {
            // Update categories
            // First, delete existing categories
            $stmt = $pdo->prepare('DELETE FROM post_categories WHERE post_id = ?');
            $stmt->execute([$post_id]);

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

            $success = 'Post updated successfully.';
            // Refresh post data
            $stmt = $pdo->prepare('SELECT * FROM posts WHERE id = ?');
            $stmt->execute([$post_id]);
            $post = $stmt->fetch();

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

<h2>Edit Post</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_post.php?id=<?php echo $post_id; ?>" method="POST">
    <div class="form-group">
        <label for="title">Post Title:</label>
        <input type="text" id="title" name="title" value="<?php echo htmlspecialchars($post['title']); ?>" required>
    </div>

    <div class="form-group">
        <label for="content">Post Content:</label>
        <textarea id="content" name="content" rows="10" required><?php echo htmlspecialchars($post['content']); ?></textarea>
    </div>

    <div class="form-group">
        <label for="status">Post Status:</label>
        <select id="status" name="status">
            <option value="draft" <?php if ($post['status'] === 'draft') echo 'selected'; ?>>Draft</option>
            <option value="published" <?php if ($post['status'] === 'published') echo 'selected'; ?>>Published</option>
        </select>
    </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 Post</button>
</form>

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

10. Deleting a Post (public/delete_post.php)

Create a delete_post.php file inside the public directory to handle post deletion.

<?php
// public/delete_post.php

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

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

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

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

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

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

// For Authors, ensure they can only delete their own posts
if ($user_role === 'author' && $post['user_id'] !== $_SESSION['user_id']) {
    echo "<p>You do not have permission to delete this post.</p>";
    exit;
}

// Delete post categories
$stmt = $pdo->prepare('DELETE FROM post_categories WHERE post_id = ?');
$stmt->execute([$post_id]);

// Delete post from database
$stmt = $pdo->prepare('DELETE FROM posts WHERE id = ?');
$stmt->execute([$post_id]);

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

11. Managing Categories (public/manage_categories.php)

Create a manage_categories.php file inside the public directory to handle category management.

<?php
// public/manage_categories.php

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

// Check if user is logged in and has Admin or Editor role
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 manage categories.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

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

// Handle category creation
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_category'])) {
    $category_name = trim($_POST['category_name']);

    if (empty($category_name)) {
        $errors[] = 'Category name is required.';
    } else {
        // Insert into database
        $stmt = $pdo->prepare('INSERT INTO categories (name) VALUES (?)');
        try {
            $stmt->execute([$category_name]);
            $success = 'Category created successfully.';
        } catch (PDOException $e) {
            if ($e->getCode() == 23000) { // Integrity constraint violation
                $errors[] = 'Category name already exists.';
            } else {
                $errors[] = 'There was an error creating the category.';
            }
        }
    }
}

// Handle category deletion
if (isset($_GET['delete_category'])) {
    $delete_category_id = (int)$_GET['delete_category'];

    // Delete category
    $stmt = $pdo->prepare('DELETE FROM categories WHERE id = ?');
    $stmt->execute([$delete_category_id]);
    $success = 'Category deleted successfully.';
}

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

<h2>Manage Categories</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; ?>

<h3>Create New Category</h3>
<form action="manage_categories.php" method="POST">
    <div class="form-group">
        <label for="category_name">Category Name:</label>
        <input type="text" id="category_name" name="category_name" required>
    </div>
    <button type="submit" name="create_category">Create Category</button>
</form>

<h3>Existing Categories</h3>
<table class="table">
    <thead>
        <tr>
            <th>ID</th>
            <th>Name</th>
            <th>Actions</th>
        </tr>
    </thead>
    <tbody>
        <?php foreach ($categories as $category): ?>
            <tr>
                <td><?php echo htmlspecialchars($category['id']); ?></td>
                <td><?php echo htmlspecialchars($category['name']); ?></td>
                <td>
                    <a href="manage_categories.php?delete_category=<?php echo $category['id']; ?>" onclick="return confirm('Are you sure you want to delete this category?');">Delete</a>
                </td>
            </tr>
        <?php endforeach; ?>
    </tbody>
</table>

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

12. Viewing a Single Post (public/view_post.php)

Create a view_post.php file inside the public directory to display individual posts.

<?php
// public/view_post.php

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

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

// Fetch post details along with author's username and categories
$stmt = $pdo->prepare('SELECT posts.*, users.username FROM posts JOIN users ON posts.user_id = users.id WHERE posts.id = ?');
$stmt->execute([$post_id]);
$post = $stmt->fetch();

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

if ($post['status'] !== 'published' && (!isset($_SESSION['user_id']) || ($_SESSION['user_id'] !== $post['user_id'] && $_SESSION['role'] !== 'admin'))) {
    echo "<p>This post is not available.</p>";
    require_once __DIR__ . '/../templates/footer.php';
    exit;
}

// Fetch categories
$stmt = $pdo->prepare('SELECT categories.name FROM categories JOIN post_categories ON categories.id = post_categories.category_id WHERE post_categories.post_id = ?');
$stmt->execute([$post_id]);
$categories = $stmt->fetchAll(PDO::FETCH_COLUMN);
?>

<h2><?php echo htmlspecialchars($post['title']); ?></h2>
<p><strong>By:</strong> <?php echo htmlspecialchars($post['username']); ?> | <strong>Published on:</strong> <?php echo htmlspecialchars($post['created_at']); ?></p>
<p><strong>Categories:</strong> <?php echo htmlspecialchars(implode(', ', $categories)); ?></p>

<?php if ($post['content']): ?>
    <div>
        <?php echo nl2br(htmlspecialchars($post['content'])); ?>
    </div>
<?php endif; ?>

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

13. Running the Application

  1. Start Apache and MySQL:
    • Ensure that the Apache and MySQL modules are running in XAMPP.
  2. Access the CMS:
    • Navigate to http://localhost/cms/public/index.php in your browser.
    • Register a new user or log in with existing credentials.
  3. Manage Content:
    • Use the dashboard to create, edit, and delete posts.
    • Manage categories as needed.
  4. View Posts:
    • Access http://localhost/cms/public/view_post.php?id={post_id} to view individual posts.

14. Deployment Considerations

When deploying your CMS to a live server, consider the following:

  • Secure Authentication:
    • Implement stronger password policies and consider using password reset functionalities.
    • Use HTTPS to encrypt data transmission, especially for login and content management.
  • Input Validation and Sanitization:
    • Thoroughly validate and sanitize all user inputs to prevent SQL injection and XSS attacks.
  • User Roles and Permissions:
    • Ensure that role-based access controls are enforced correctly to prevent unauthorized access.
  • Error Handling:
    • Provide user-friendly error messages without exposing sensitive information.
  • Regular Backups:
    • Schedule regular backups of your database and application files to prevent data loss.
  • Scalability:
    • Design your CMS to handle increased traffic and content volume as your website grows.
  • SEO Optimization:
    • Implement SEO-friendly URLs, meta tags, and sitemaps to enhance search engine visibility.
  • Performance Optimization:
    • Optimize database queries, use caching strategies, and compress assets to improve load times.
  • Security Enhancements:
    • Implement measures like CSRF tokens in forms, secure session handling, and regular security audits.

15. Enhancements and Best Practices

  • Rich Text Editor:
    • Integrate a rich text editor (e.g., TinyMCE, CKEditor) for better post formatting and user experience.
  • Media Management:
    • Implement functionalities to upload and manage media files like images and videos.
  • Comment System:
    • Allow users to comment on posts, with moderation capabilities for admins.
  • User Profiles:
    • Create user profiles where authors can manage their own posts and view their activity.
  • Search Functionality:
    • Add a search bar to help users find posts based on keywords or categories.
  • Pagination:
    • Implement pagination for post listings to handle a large number of posts efficiently.
  • Responsive Design:
    • Ensure that the CMS interface is mobile-friendly and responsive across various devices.
  • Backup and Recovery:
    • Implement automated backup and recovery solutions to safeguard data.
  • Use of Frameworks:
    • Consider using PHP frameworks like Laravel or Symfony to leverage built-in features and streamline development.
  • Analytics Integration:
    • Integrate analytics tools to monitor website traffic and user behavior.
  • SEO Enhancements:
    • Implement SEO best practices to improve search engine rankings and visibility.

Conclusion

These next three PHP use cases—Building a RESTful API, Creating a Real-Time Chat Application, and Developing a Content Management System (CMS)—provide a comprehensive understanding of essential web development functionalities using PHP and MySQL. Each project is meticulously structured to guide your followers through the entire development process, from setup to deployment, ensuring they gain practical experience and confidence in building secure and functional web applications.

Next Steps for Your Followers:

  1. Implement the Projects:
    • Follow the step-by-step guides to build each application.
    • Experiment with the code to better understand how each component works.
  2. Enhance the Projects:
    • Add new features or improve existing ones.
    • Implement security best practices to safeguard applications.
  3. Explore PHP Frameworks:
    • Once comfortable with vanilla PHP, consider exploring frameworks like Laravel, Symfony, or CodeIgniter for more advanced projects.
  4. Engage with the Community:
    • Encourage followers to share their progress, ask questions, and collaborate on improvements.
  5. Stay Updated:
    • PHP is continuously evolving. Stay informed about the latest PHP versions, features, and best practices.
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 *