Session 6.7 – Content Management Systems

Module 6: Advanced Web Technologies | Duration: 1 hr

Learning Objectives

By the end of this session, students will be able to:

  • Understand what a Content Management System (CMS) is
  • Compare different types of CMS platforms
  • Evaluate popular CMS solutions (WordPress, Drupal, Joomla)
  • Build a basic custom CMS from scratch
  • Implement essential CMS features
  • Make informed decisions when choosing a CMS

Introduction to CMS

A Content Management System (CMS) is software that helps users create, manage, and modify content on a website without requiring specialized technical knowledge.

What is a CMS?

A CMS provides a user-friendly interface that allows content creators, editors, and administrators to manage website content without writing code. It separates content from design and provides tools for collaboration and workflow management.

  • Content Creation: WYSIWYG editors, media management
  • Organization: Categories, tags, taxonomies
  • Publishing: Scheduling, versioning, workflow
  • User Management: Roles, permissions, authentication
  • Extensibility: Plugins, themes, modules
CMS Architecture
Presentation Layer
Themes, Templates, Frontend
Application Layer
Business Logic, APIs, Plugins
Content Repository
Database, File Storage, Media

Types of CMS

Traditional/Coupled CMS

Examples: WordPress, Drupal, Joomla

Characteristics:

  • Backend and frontend tightly coupled
  • Content and presentation together
  • Full-stack solution
  • Template-based rendering

Best for: Traditional websites, blogs, small to medium sites

Headless CMS

Examples: Contentful, Strapi, Sanity

Characteristics:

  • Backend-only, no presentation layer
  • Content delivered via API
  • Frontend framework agnostic
  • Multi-channel content delivery

Best for: Mobile apps, SPAs, multiple frontends

Decoupled CMS

Characteristics:

  • Hybrid approach
  • Backend and frontend separated
  • Can use either coupled or headless
  • More flexible architecture

Best for: Complex enterprise applications

Static Site Generators

Examples: Jekyll, Hugo, Gatsby

Characteristics:

  • Generates static HTML files
  • No database at runtime
  • Extremely fast performance
  • Version control friendly

Best for: Documentation, blogs, marketing sites

Building a Simple CMS

Database Schema
-- Users table CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password VARCHAR(255) NOT NULL, role ENUM('admin', 'editor', 'author') DEFAULT 'author', created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Posts table CREATE TABLE posts ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(200) NOT NULL, slug VARCHAR(200) UNIQUE NOT NULL, content TEXT, excerpt TEXT, author_id INT NOT NULL, status ENUM('draft', 'published', 'archived') DEFAULT 'draft', published_at TIMESTAMP NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, FOREIGN KEY (author_id) REFERENCES users(id) ); -- Categories table CREATE TABLE categories ( id INT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(100) NOT NULL, slug VARCHAR(100) UNIQUE NOT NULL, description TEXT ); -- Post-Category relationship CREATE TABLE post_categories ( post_id INT NOT NULL, category_id INT NOT NULL, PRIMARY KEY (post_id, category_id), FOREIGN KEY (post_id) REFERENCES posts(id) ON DELETE CASCADE, FOREIGN KEY (category_id) REFERENCES categories(id) ON DELETE CASCADE ); -- Media table CREATE TABLE media ( id INT AUTO_INCREMENT PRIMARY KEY, filename VARCHAR(255) NOT NULL, filepath VARCHAR(255) NOT NULL, mime_type VARCHAR(100), filesize INT, uploaded_by INT NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (uploaded_by) REFERENCES users(id) );
Post Management
<?php // Post.php class Post { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; } // Create new post public function create($data) { $sql = "INSERT INTO posts (title, slug, content, excerpt, author_id, status) VALUES (:title, :slug, :content, :excerpt, :author_id, :status)"; $stmt = $this->pdo->prepare($sql); return $stmt->execute([ ':title' => $data['title'], ':slug' => $this->createSlug($data['title']), ':content' => $data['content'], ':excerpt' => $data['excerpt'] ?? '', ':author_id' => $data['author_id'], ':status' => $data['status'] ?? 'draft' ]); } // Get all posts with pagination public function getAll($page = 1, $perPage = 10, $status = 'published') { $offset = ($page - 1) * $perPage; $sql = "SELECT p.*, u.username as author_name FROM posts p JOIN users u ON p.author_id = u.id WHERE p.status = :status ORDER BY p.created_at DESC LIMIT :offset, :perPage"; $stmt = $this->pdo->prepare($sql); $stmt->bindValue(':status', $status); $stmt->bindValue(':offset', $offset, PDO::PARAM_INT); $stmt->bindValue(':perPage', $perPage, PDO::PARAM_INT); $stmt->execute(); return $stmt->fetchAll(PDO::FETCH_ASSOC); } // Get single post by slug public function getBySlug($slug) { $sql = "SELECT p.*, u.username as author_name FROM posts p JOIN users u ON p.author_id = u.id WHERE p.slug = :slug AND p.status = 'published'"; $stmt = $this->pdo->prepare($sql); $stmt->execute([':slug' => $slug]); return $stmt->fetch(PDO::FETCH_ASSOC); } // Update post public function update($id, $data) { $sql = "UPDATE posts SET title = :title, slug = :slug, content = :content, excerpt = :excerpt, status = :status, updated_at = NOW() WHERE id = :id"; $stmt = $this->pdo->prepare($sql); return $stmt->execute([ ':title' => $data['title'], ':slug' => $this->createSlug($data['title']), ':content' => $data['content'], ':excerpt' => $data['excerpt'] ?? '', ':status' => $data['status'], ':id' => $id ]); } // Delete post public function delete($id) { $stmt = $this->pdo->prepare("DELETE FROM posts WHERE id = ?"); return $stmt->execute([$id]); } // Create URL-friendly slug private function createSlug($title) { $slug = strtolower(trim(preg_replace('/[^A-Za-z0-9-]+/', '-', $title))); return $slug; } // Search posts public function search($query) { $sql = "SELECT p.*, u.username as author_name FROM posts p JOIN users u ON p.author_id = u.id WHERE (p.title LIKE :query OR p.content LIKE :query) AND p.status = 'published' ORDER BY p.created_at DESC"; $stmt = $this->pdo->prepare($sql); $stmt->execute([':query' => "%$query%"]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } } ?>
User Authentication
<?php // Auth.php class Auth { private $pdo; public function __construct($pdo) { $this->pdo = $pdo; session_start(); } // Register new user public function register($username, $email, $password) { // Validate input if (empty($username) || empty($email) || empty($password)) { return ['success' => false, 'message' => 'All fields required']; } // Hash password $hashedPassword = password_hash($password, PASSWORD_DEFAULT); try { $sql = "INSERT INTO users (username, email, password) VALUES (?, ?, ?)"; $stmt = $this->pdo->prepare($sql); $stmt->execute([$username, $email, $hashedPassword]); return ['success' => true, 'message' => 'Registration successful']; } catch (PDOException $e) { return ['success' => false, 'message' => 'Username or email already exists']; } } // Login user public function login($username, $password) { $sql = "SELECT * FROM users WHERE username = ? OR email = ?"; $stmt = $this->pdo->prepare($sql); $stmt->execute([$username, $username]); $user = $stmt->fetch(PDO::FETCH_ASSOC); if ($user && password_verify($password, $user['password'])) { $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; $_SESSION['role'] = $user['role']; return ['success' => true, 'message' => 'Login successful']; } return ['success' => false, 'message' => 'Invalid credentials']; } // Logout user public function logout() { session_destroy(); } // Check if user is logged in public function isLoggedIn() { return isset($_SESSION['user_id']); } // Get current user public function getCurrentUser() { if (!$this->isLoggedIn()) { return null; } $sql = "SELECT id, username, email, role FROM users WHERE id = ?"; $stmt = $this->pdo->prepare($sql); $stmt->execute([$_SESSION['user_id']]); return $stmt->fetch(PDO::FETCH_ASSOC); } // Check user permission public function hasPermission($requiredRole) { if (!$this->isLoggedIn()) { return false; } $userRole = $_SESSION['role']; $roleHierarchy = ['admin' => 3, 'editor' => 2, 'author' => 1]; return $roleHierarchy[$userRole] >= $roleHierarchy[$requiredRole]; } } ?>
Admin Interface
<?php // admin/posts.php require_once '../config/database.php'; require_once '../classes/Auth.php'; require_once '../classes/Post.php'; $auth = new Auth($pdo); // Check if user is logged in and has permission if (!$auth->isLoggedIn() || !$auth->hasPermission('author')) { header('Location: login.php'); exit; } $post = new Post($pdo); $currentUser = $auth->getCurrentUser(); ?> <!DOCTYPE html> <html> <head> <title>Manage Posts - CMS Admin</title> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet"> </head> <body> <nav class="navbar navbar-expand-lg navbar-dark bg-dark"> <div class="container-fluid"> <a class="navbar-brand" href="#">CMS Admin</a> <div class="navbar-nav ms-auto"> <span class="navbar-text me-3">Welcome, <?php echo $currentUser['username']; ?></span> <a href="logout.php" class="btn btn-outline-light btn-sm">Logout</a> </div> </div> </nav> <div class="container-fluid"> <div class="row"> <!-- Sidebar --> <nav class="col-md-2 d-md-block bg-light sidebar"> <div class="position-sticky pt-3"> <ul class="nav flex-column"> <li class="nav-item"> <a class="nav-link" href="dashboard.php">Dashboard</a> </li> <li class="nav-item"> <a class="nav-link active" href="posts.php">Posts</a> </li> <li class="nav-item"> <a class="nav-link" href="categories.php">Categories</a> </li> <li class="nav-item"> <a class="nav-link" href="media.php">Media</a> </li> <?php if ($auth->hasPermission('admin')): ?> <li class="nav-item"> <a class="nav-link" href="users.php">Users</a> </li> <?php endif; ?> </ul> </div> </nav> <!-- Main content --> <main class="col-md-10 ms-sm-auto px-md-4"> <div class="d-flex justify-content-between align-items-center pt-3 pb-2 mb-3 border-bottom"> <h1 class="h2">Posts</h1> <a href="post-create.php" class="btn btn-primary">New Post</a> </div> <div class="table-responsive"> <table class="table table-striped"> <thead> <tr> <th>Title</th> <th>Author</th> <th>Status</th> <th>Date</th> <th>Actions</th> </tr> </thead> <tbody> <?php $posts = $post->getAll(1, 20, 'all'); foreach ($posts as $p): ?> <tr> <td><?php echo htmlspecialchars($p['title']); ?></td> <td><?php echo htmlspecialchars($p['author_name']); ?></td> <td> <span class="badge bg-<?php echo $p['status'] === 'published' ? 'success' : 'warning'; ?>"> <?php echo $p['status']; ?> </span> </td> <td><?php echo date('Y-m-d', strtotime($p['created_at'])); ?></td> <td> <a href="post-edit.php?id=<?php echo $p['id']; ?>" class="btn btn-sm btn-warning">Edit</a> <a href="post-delete.php?id=<?php echo $p['id']; ?>" class="btn btn-sm btn-danger" onclick="return confirm('Delete this post?')">Delete</a> </td> </tr> <?php endforeach; ?> </tbody> </table> </div> </main> </div> </div> </body> </html>

Essential CMS Features

Content Editor
  • WYSIWYG editor (TinyMCE, CKEditor)
  • Markdown support
  • Media insertion
  • HTML source editing
Media Management
  • File upload and organization
  • Image resizing and cropping
  • Media library
  • Multiple file formats
User Management
  • Role-based access control
  • User registration and profiles
  • Permission management
  • Activity logging
SEO Tools
  • Meta tags management
  • URL customization
  • Sitemap generation
  • Analytics integration

Choosing the Right CMS

Key Considerations
  • Ease of Use: User skill level
  • Flexibility: Customization needs
  • Scalability: Growth potential
  • Security: Built-in security features
  • Cost: Licensing, hosting, maintenance
  • Community: Support and resources
  • Extensions: Plugin availability
  • Performance: Speed and optimization
  • Integration: Third-party services
  • Multi-language: Internationalization
Small Projects

Best Choice: WordPress, Ghost

Why: Easy setup, low cost, good for blogs and small business sites

Enterprise Projects

Best Choice: Drupal, custom solution

Why: Advanced features, security, scalability, complex requirements

Modern Apps

Best Choice: Headless CMS (Contentful, Strapi)

Why: API-first, multi-platform, modern development workflow

Session Summary

Key Points
  • CMS separates content management from presentation
  • Different types: Traditional, Headless, Decoupled, Static
  • WordPress dominates with 43% market share
  • Essential features: content editor, media management, user roles, SEO
  • Building custom CMS requires database design and CRUD operations
  • Role-based access control is crucial for security
  • Choose CMS based on project size, requirements, and team skills
  • Consider scalability, security, and extensibility
  • Modern projects often benefit from headless CMS architecture
Next Session Preview

In the next session, we will dive deep into WordPress Development, learning how to create themes, plugins, and customize WordPress for various use cases.