Discussion Forum Website built with HTML, CSS, JavaScript, PHP, and MySQL

💬 Project Introduction: Discussion Forum Website

This project is a fully functional Discussion Forum Website designed for online communities to engage in conversations, ask questions, share knowledge, and build discussions around various topics. It provides a Reddit-like or Stack Overflow-like experience with categories, threads, posts, and user reputation systems.

The Problem it Solves:
Online communities need a platform where users can post questions, share answers, and engage in discussions. This system provides a structured way to organize conversations by categories, track user contributions, and maintain a healthy community through moderation tools.

Key Features:

  • User Features:
  • User registration and login system
  • Create new discussion threads
  • Post replies to existing threads
  • Upvote/downvote posts (karma system)
  • Mark best answers (solved threads)
  • User profiles with post history
  • Search functionality
  • Subscribe to threads for notifications
  • Report inappropriate content
  • Admin Features:
  • Dashboard with community statistics
  • Category management (create, edit, delete)
  • User management (ban, promote to moderator)
  • Post moderation (edit, delete, feature)
  • Reported content review
  • Site settings configuration
  • Announcement system

Technology Stack:

  • Frontend: HTML5, CSS3, JavaScript (Fetch API, Markdown parser)
  • Backend: PHP (Object-Oriented PHP with PDO)
  • Database: MySQL
  • Additional Libraries:
  • Parsedown (Markdown parsing)
  • PHPMailer (Email notifications)

📁 Project File Structure

discussion-forum/
│
├── index.php                 # Homepage - Recent discussions
├── topics.php                # Browse topics by category
├── thread.php                # View single thread with replies
├── create-thread.php         # Create new discussion thread
├── edit-post.php             # Edit post
├── search.php                # Search results
├── profile.php               # User profile
├── login.php                 # User login
├── register.php              # User registration
├── logout.php                # Logout script
│
├── admin/                    # Admin Panel
│   ├── index.php             # Admin login
│   ├── dashboard.php         # Admin dashboard
│   ├── categories.php        # Manage categories
│   ├── users.php             # Manage users
│   ├── posts.php             # Moderate posts
│   ├── reports.php           # Handle reports
│   ├── settings.php          # Forum settings
│   └── logout.php            # Admin logout
│
├── includes/                  # Backend logic
│   ├── config.php             # Database connection
│   ├── functions.php          # Helper functions
│   ├── auth.php               # Authentication functions
│   ├── session.php            # Session management
│   └── mailer.php             # Email functions
│
├── api/                        # AJAX endpoints
│   ├── vote.php                # Handle upvotes/downvotes
│   ├── report.php              # Submit reports
│   ├── subscribe.php           # Thread subscriptions
│   └── search.php              # Live search
│
├── assets/                    # Static assets
│   ├── css/
│   │   ├── style.css          # Main styles
│   │   └── admin.css           # Admin styles
│   ├── js/
│   │   ├── main.js            # Main JavaScript
│   │   ├── markdown.js        # Markdown preview
│   │   └── voting.js          # Voting system
│   ├── images/
│   │   ├── avatars/           # User avatars
│   │   └── default-avatar.png # Default avatar
│   └── vendor/
│       └── parsedown/          # Markdown parser
│
└── database/
└── forum.sql               # Database dump

🗄️ Database Setup (database/forum.sql)

Create a database named discussion_forum and run this SQL.

-- phpMyAdmin SQL Dump
-- Database: `discussion_forum`
CREATE DATABASE IF NOT EXISTS `discussion_forum`;
USE `discussion_forum`;
-- --------------------------------------------------------
-- Table structure for table `users`
-- --------------------------------------------------------
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`username` varchar(50) NOT NULL,
`email` varchar(100) NOT NULL,
`password` varchar(255) NOT NULL,
`full_name` varchar(100) DEFAULT NULL,
`bio` text DEFAULT NULL,
`avatar` varchar(255) DEFAULT 'default-avatar.png',
`role` enum('admin','moderator','member') DEFAULT 'member',
`karma` int(11) DEFAULT 0,
`posts_count` int(11) DEFAULT 0,
`joined_at` timestamp NOT NULL DEFAULT current_timestamp(),
`last_seen` timestamp NULL DEFAULT NULL,
`status` enum('active','banned','inactive') DEFAULT 'active',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`),
UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default admin (password: admin123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`) VALUES
('admin', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Administrator', 'admin');
-- Sample moderator (password: mod123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`) VALUES
('moderator', '[email protected]', '$2y$10$YourHashedPasswordHere', 'Moderator', 'moderator');
-- Sample member (password: member123)
INSERT INTO `users` (`username`, `email`, `password`, `full_name`, `role`) VALUES
('john_doe', '[email protected]', '$2y$10$YourHashedPasswordHere', 'John Doe', 'member');
-- --------------------------------------------------------
-- Table structure for table `categories`
-- --------------------------------------------------------
CREATE TABLE `categories` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`slug` varchar(100) NOT NULL,
`description` text DEFAULT NULL,
`icon` varchar(50) DEFAULT '📁',
`color` varchar(20) DEFAULT '#3498db',
`threads_count` int(11) DEFAULT 0,
`posts_count` int(11) DEFAULT 0,
`last_post_id` int(11) DEFAULT NULL,
`last_post_time` timestamp NULL DEFAULT NULL,
`order` int(11) DEFAULT 0,
`status` enum('active','hidden') DEFAULT 'active',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Sample categories
INSERT INTO `categories` (`name`, `slug`, `description`, `icon`, `color`) VALUES
('General Discussion', 'general', 'General discussions about anything and everything', '💬', '#3498db'),
('Technology', 'technology', 'Discuss latest tech trends, programming, and gadgets', '💻', '#2ecc71'),
('Gaming', 'gaming', 'Video games, gaming news, and discussions', '🎮', '#e74c3c'),
('Science', 'science', 'Scientific discoveries, research, and discussions', '🔬', '#9b59b6'),
('Health & Fitness', 'health', 'Health tips, fitness advice, and wellness', '💪', '#e67e22'),
('Business', 'business', 'Business strategies, entrepreneurship, and finance', '💼', '#f1c40f');
-- --------------------------------------------------------
-- Table structure for table `threads`
-- --------------------------------------------------------
CREATE TABLE `threads` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`slug` varchar(255) NOT NULL,
`category_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`views` int(11) DEFAULT 0,
`replies_count` int(11) DEFAULT 0,
`last_post_id` int(11) DEFAULT NULL,
`last_post_time` timestamp NULL DEFAULT NULL,
`is_pinned` tinyint(1) DEFAULT 0,
`is_locked` tinyint(1) DEFAULT 0,
`status` enum('active','hidden','deleted') DEFAULT 'active',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `slug` (`slug`),
KEY `category_id` (`category_id`),
KEY `user_id` (`user_id`),
KEY `last_post_id` (`last_post_id`),
CONSTRAINT `threads_ibfk_1` FOREIGN KEY (`category_id`) REFERENCES `categories` (`id`) ON DELETE CASCADE,
CONSTRAINT `threads_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `posts`
-- --------------------------------------------------------
CREATE TABLE `posts` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`thread_id` int(11) NOT NULL,
`user_id` int(11) NOT NULL,
`content` longtext NOT NULL,
`is_first_post` tinyint(1) DEFAULT 0,
`is_best_answer` tinyint(1) DEFAULT 0,
`upvotes` int(11) DEFAULT 0,
`downvotes` int(11) DEFAULT 0,
`score` int(11) DEFAULT 0,
`ip_address` varchar(45) DEFAULT NULL,
`status` enum('active','hidden','deleted') DEFAULT 'active',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`updated_at` timestamp NULL DEFAULT NULL ON UPDATE current_timestamp(),
PRIMARY KEY (`id`),
KEY `thread_id` (`thread_id`),
KEY `user_id` (`user_id`),
CONSTRAINT `posts_ibfk_1` FOREIGN KEY (`thread_id`) REFERENCES `threads` (`id`) ON DELETE CASCADE,
CONSTRAINT `posts_ibfk_2` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `votes`
-- --------------------------------------------------------
CREATE TABLE `votes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`post_id` int(11) NOT NULL,
`vote_type` enum('upvote','downvote') NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `user_post` (`user_id`,`post_id`),
KEY `post_id` (`post_id`),
CONSTRAINT `votes_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `votes_ibfk_2` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `subscriptions`
-- --------------------------------------------------------
CREATE TABLE `subscriptions` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`thread_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `user_thread` (`user_id`,`thread_id`),
KEY `thread_id` (`thread_id`),
CONSTRAINT `subscriptions_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `subscriptions_ibfk_2` FOREIGN KEY (`thread_id`) REFERENCES `threads` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `reports`
-- --------------------------------------------------------
CREATE TABLE `reports` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`post_id` int(11) NOT NULL,
`reported_by` int(11) NOT NULL,
`reason` enum('spam','harassment','inappropriate','offensive','other') NOT NULL,
`details` text DEFAULT NULL,
`status` enum('pending','reviewed','resolved') DEFAULT 'pending',
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
`resolved_at` timestamp NULL DEFAULT NULL,
`resolved_by` int(11) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `post_id` (`post_id`),
KEY `reported_by` (`reported_by`),
KEY `resolved_by` (`resolved_by`),
CONSTRAINT `reports_ibfk_1` FOREIGN KEY (`post_id`) REFERENCES `posts` (`id`) ON DELETE CASCADE,
CONSTRAINT `reports_ibfk_2` FOREIGN KEY (`reported_by`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `reports_ibfk_3` FOREIGN KEY (`resolved_by`) REFERENCES `users` (`id`) ON DELETE SET NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `bookmarks`
-- --------------------------------------------------------
CREATE TABLE `bookmarks` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`thread_id` int(11) NOT NULL,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
UNIQUE KEY `user_thread` (`user_id`,`thread_id`),
KEY `thread_id` (`thread_id`),
CONSTRAINT `bookmarks_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE,
CONSTRAINT `bookmarks_ibfk_2` FOREIGN KEY (`thread_id`) REFERENCES `threads` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `notifications`
-- --------------------------------------------------------
CREATE TABLE `notifications` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL,
`type` varchar(50) NOT NULL,
`subject` varchar(255) NOT NULL,
`content` text DEFAULT NULL,
`link` varchar(255) DEFAULT NULL,
`is_read` tinyint(1) DEFAULT 0,
`created_at` timestamp NOT NULL DEFAULT current_timestamp(),
PRIMARY KEY (`id`),
KEY `user_id` (`user_id`),
KEY `is_read` (`is_read`),
CONSTRAINT `notifications_ibfk_1` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- --------------------------------------------------------
-- Table structure for table `settings`
-- --------------------------------------------------------
CREATE TABLE `settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`setting_key` varchar(100) NOT NULL,
`setting_value` text DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `setting_key` (`setting_key`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
-- Default settings
INSERT INTO `settings` (`setting_key`, `setting_value`) VALUES
('forum_title', 'Discussion Forum'),
('forum_description', 'A place for meaningful discussions'),
('forum_keywords', 'forum, discussion, community'),
('posts_per_page', '20'),
('threads_per_page', '20'),
('allow_registration', '1'),
('email_verification', '0'),
('min_username_length', '3'),
('max_username_length', '20'),
('min_password_length', '6'),
('allow_markdown', '1'),
('enable_karma', '1'),
('karma_per_upvote', '1'),
('karma_per_downvote', '-1'),
('karma_for_best_answer', '5'),
('contact_email', '[email protected]');
COMMIT;

💻 Core PHP Files

1. Database Configuration (includes/config.php)

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_NAME', 'discussion_forum');
define('DB_USER', 'root');
define('DB_PASS', '');
try {
$pdo = new PDO(
"mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8mb4",
DB_USER,
DB_PASS,
[
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
PDO::ATTR_EMULATE_PREPARES => false
]
);
} catch (PDOException $e) {
die("Connection failed: " . $e->getMessage());
}
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Load settings
$settings = [];
$stmt = $pdo->query("SELECT setting_key, setting_value FROM settings");
while ($row = $stmt->fetch()) {
$settings[$row['setting_key']] = $row['setting_value'];
}
// Forum configuration
define('FORUM_TITLE', $settings['forum_title'] ?? 'Discussion Forum');
define('FORUM_URL', 'http://localhost/discussion-forum/');
define('POSTS_PER_PAGE', $settings['posts_per_page'] ?? 20);
define('THREADS_PER_PAGE', $settings['threads_per_page'] ?? 20);
define('ENABLE_KARMA', $settings['enable_karma'] ?? 1);
define('CONTACT_EMAIL', $settings['contact_email'] ?? '[email protected]');
// Helper functions
function sanitize($data) {
$data = trim($data);
$data = stripslashes($data);
$data = htmlspecialchars($data);
return $data;
}
function redirect($url) {
header("Location: $url");
exit;
}
function isLoggedIn() {
return isset($_SESSION['user_id']);
}
function isAdmin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
function isModerator() {
return isset($_SESSION['user_role']) && in_array($_SESSION['user_role'], ['admin', 'moderator']);
}
function timeAgo($timestamp) {
$time_ago = strtotime($timestamp);
$current_time = time();
$time_difference = $current_time - $time_ago;
$seconds = $time_difference;
$minutes = round($seconds / 60);
$hours = round($seconds / 3600);
$days = round($seconds / 86400);
$weeks = round($seconds / 604800);
$months = round($seconds / 2629440);
$years = round($seconds / 31553280);
if ($seconds <= 60) {
return "Just now";
} else if ($minutes <= 60) {
return ($minutes == 1) ? "1 minute ago" : "$minutes minutes ago";
} else if ($hours <= 24) {
return ($hours == 1) ? "1 hour ago" : "$hours hours ago";
} else if ($days <= 7) {
return ($days == 1) ? "yesterday" : "$days days ago";
} else if ($weeks <= 4.3) {
return ($weeks == 1) ? "1 week ago" : "$weeks weeks ago";
} else if ($months <= 12) {
return ($months == 1) ? "1 month ago" : "$months months ago";
} else {
return ($years == 1) ? "1 year ago" : "$years years ago";
}
}
function createSlug($string) {
$string = strtolower($string);
$string = preg_replace('/[^a-z0-9-]/', '-', $string);
$string = preg_replace('/-+/', '-', $string);
return trim($string, '-');
}
function getGravatar($email, $size = 80) {
$hash = md5(strtolower(trim($email)));
return "https://www.gravatar.com/avatar/$hash?s=$size&d=identicon";
}
function updateUserKarma($user_id, $change) {
global $pdo;
$pdo->prepare("UPDATE users SET karma = karma + ? WHERE id = ?")->execute([$change, $user_id]);
}
function sendNotification($user_id, $type, $subject, $content, $link = null) {
global $pdo;
$stmt = $pdo->prepare("INSERT INTO notifications (user_id, type, subject, content, link) VALUES (?, ?, ?, ?, ?)");
return $stmt->execute([$user_id, $type, $subject, $content, $link]);
}
function getUnreadNotificationCount($user_id) {
global $pdo;
$stmt = $pdo->prepare("SELECT COUNT(*) FROM notifications WHERE user_id = ? AND is_read = 0");
$stmt->execute([$user_id]);
return $stmt->fetchColumn();
}
?>

2. User Authentication (includes/auth.php)

<?php
require_once 'config.php';
class Auth {
public static function login($username, $password) {
global $pdo;
$stmt = $pdo->prepare("SELECT * FROM users WHERE username = ? OR email = ?");
$stmt->execute([$username, $username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password'])) {
if ($user['status'] === 'banned') {
return ['success' => false, 'message' => 'Your account has been banned'];
}
if ($user['status'] === 'inactive') {
return ['success' => false, 'message' => 'Your account is inactive'];
}
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_name'] = $user['username'];
$_SESSION['user_role'] = $user['role'];
$_SESSION['user_avatar'] = $user['avatar'];
$_SESSION['user_karma'] = $user['karma'];
// Update last seen
$pdo->prepare("UPDATE users SET last_seen = NOW() WHERE id = ?")->execute([$user['id']]);
return ['success' => true, 'user' => $user];
}
return ['success' => false, 'message' => 'Invalid username or password'];
}
public static function register($data) {
global $pdo;
// Validate username
if (strlen($data['username']) < $GLOBALS['settings']['min_username_length'] ?? 3) {
return ['success' => false, 'message' => 'Username too short'];
}
if (strlen($data['username']) > $GLOBALS['settings']['max_username_length'] ?? 20) {
return ['success' => false, 'message' => 'Username too long'];
}
if (!preg_match('/^[a-zA-Z0-9_]+$/', $data['username'])) {
return ['success' => false, 'message' => 'Username can only contain letters, numbers, and underscores'];
}
// Check if username exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE username = ?");
$stmt->execute([$data['username']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Username already taken'];
}
// Check if email exists
$stmt = $pdo->prepare("SELECT id FROM users WHERE email = ?");
$stmt->execute([$data['email']]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'Email already registered'];
}
// Validate password
if (strlen($data['password']) < $GLOBALS['settings']['min_password_length'] ?? 6) {
return ['success' => false, 'message' => 'Password too short'];
}
// Hash password
$hashedPassword = password_hash($data['password'], PASSWORD_DEFAULT);
// Insert user
$stmt = $pdo->prepare("
INSERT INTO users (username, email, password, full_name, bio, avatar) 
VALUES (?, ?, ?, ?, ?, ?)
");
$avatar = 'default-avatar.png';
$success = $stmt->execute([
$data['username'],
$data['email'],
$hashedPassword,
$data['full_name'] ?? null,
$data['bio'] ?? null,
$avatar
]);
if ($success) {
return ['success' => true, 'message' => 'Registration successful! Please login.'];
}
return ['success' => false, 'message' => 'Registration failed'];
}
public static function logout() {
session_destroy();
redirect('login.php');
}
public static function checkLogin() {
if (!isset($_SESSION['user_id'])) {
redirect('login.php');
}
}
public static function checkModerator() {
self::checkLogin();
if (!isModerator()) {
redirect('index.php');
}
}
public static function getCurrentUser() {
global $pdo;
if (!isset($_SESSION['user_id'])) {
return null;
}
$stmt = $pdo->prepare("SELECT * FROM users WHERE id = ?");
$stmt->execute([$_SESSION['user_id']]);
return $stmt->fetch();
}
}
?>

3. Homepage (index.php)

<?php
require_once 'includes/config.php';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * THREADS_PER_PAGE;
// Get categories for sidebar
$categories = $pdo->query("
SELECT c.*, 
(SELECT COUNT(*) FROM threads WHERE category_id = c.id) as thread_count
FROM categories c
WHERE c.status = 'active'
ORDER BY c.order, c.name
")->fetchAll();
// Get recent threads
$recent_threads = $pdo->prepare("
SELECT t.*, 
u.username as author, u.avatar as author_avatar,
c.name as category_name, c.slug as category_slug,
(SELECT COUNT(*) FROM posts WHERE thread_id = t.id) as post_count,
(SELECT id FROM posts WHERE thread_id = t.id ORDER BY created_at DESC LIMIT 1) as last_post_id,
(SELECT created_at FROM posts WHERE thread_id = t.id ORDER BY created_at DESC LIMIT 1) as last_post_time
FROM threads t
JOIN users u ON t.user_id = u.id
JOIN categories c ON t.category_id = c.id
WHERE t.status = 'active'
ORDER BY t.last_post_time DESC, t.created_at DESC
LIMIT ? OFFSET ?
");
$recent_threads->execute([THREADS_PER_PAGE, $offset]);
$threads = $recent_threads->fetchAll();
// Get total threads count for pagination
$total_threads = $pdo->query("SELECT COUNT(*) FROM threads WHERE status = 'active'")->fetchColumn();
$total_pages = ceil($total_threads / THREADS_PER_PAGE);
// Get stats
$total_users = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$total_threads_count = $pdo->query("SELECT COUNT(*) FROM threads")->fetchColumn();
$total_posts_count = $pdo->query("SELECT COUNT(*) FROM posts")->fetchColumn();
$online_users = $pdo->query("SELECT COUNT(*) FROM users WHERE last_seen > DATE_SUB(NOW(), INTERVAL 15 MINUTE)")->fetchColumn();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo FORUM_TITLE; ?> - Home</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header -->
<header class="forum-header">
<div class="container">
<div class="header-top">
<div class="logo">
<h1><a href="index.php"><?php echo FORUM_TITLE; ?></a></h1>
</div>
<div class="search-bar">
<form action="search.php" method="GET">
<input type="text" name="q" placeholder="Search discussions..." id="search-input">
<button type="submit">🔍</button>
</form>
</div>
<div class="user-menu">
<?php if (isLoggedIn()): ?>
<div class="user-dropdown">
<img src="assets/images/avatars/<?php echo $_SESSION['user_avatar']; ?>" 
alt="<?php echo $_SESSION['user_name']; ?>" class="avatar-small">
<span><?php echo $_SESSION['user_name']; ?></span>
<span class="karma-badge">⚡ <?php echo $_SESSION['user_karma']; ?></span>
<?php $unread = getUnreadNotificationCount($_SESSION['user_id']); ?>
<?php if ($unread > 0): ?>
<span class="notification-badge"><?php echo $unread; ?></span>
<?php endif; ?>
<div class="dropdown-menu">
<a href="profile.php">Profile</a>
<a href="notifications.php">Notifications</a>
<a href="bookmarks.php">Bookmarks</a>
<?php if (isModerator()): ?>
<a href="admin/dashboard.php">Moderator Panel</a>
<?php endif; ?>
<a href="logout.php">Logout</a>
</div>
</div>
<?php else: ?>
<a href="login.php" class="btn btn-small">Login</a>
<a href="register.php" class="btn btn-primary btn-small">Register</a>
<?php endif; ?>
</div>
</div>
<nav class="main-nav">
<ul>
<li><a href="index.php" class="active">Home</a></li>
<li><a href="topics.php">Topics</a></li>
<li><a href="recent.php">Recent</a></li>
<li><a href="unanswered.php">Unanswered</a></li>
<li><a href="create-thread.php" class="btn-create">+ New Discussion</a></li>
</ul>
</nav>
</div>
</header>
<main class="container">
<div class="forum-layout">
<!-- Main Content -->
<div class="forum-main">
<div class="forum-stats">
<span>📊 <?php echo number_format($total_users); ?> users</span>
<span>📝 <?php echo number_format($total_threads_count); ?> discussions</span>
<span>💬 <?php echo number_format($total_posts_count); ?> posts</span>
<span>🟢 <?php echo $online_users; ?> online</span>
</div>
<div class="thread-list">
<div class="thread-list-header">
<h2>Recent Discussions</h2>
<a href="create-thread.php" class="btn btn-primary">Start New Discussion</a>
</div>
<?php if (empty($threads)): ?>
<div class="no-threads">
<p>No discussions yet. Be the first to start one!</p>
</div>
<?php else: ?>
<?php foreach ($threads as $thread): ?>
<div class="thread-item">
<div class="thread-stats">
<div class="stat">
<span class="stat-value"><?php echo $thread['post_count']; ?></span>
<span class="stat-label">replies</span>
</div>
<div class="stat">
<span class="stat-value"><?php echo $thread['views']; ?></span>
<span class="stat-label">views</span>
</div>
</div>
<div class="thread-content">
<h3>
<a href="thread.php?slug=<?php echo $thread['slug']; ?>">
<?php if ($thread['is_pinned']): ?>📌<?php endif; ?>
<?php if ($thread['is_locked']): ?>🔒<?php endif; ?>
<?php echo $thread['title']; ?>
</a>
</h3>
<div class="thread-meta">
<span class="category-badge" style="background: <?php echo $categories[array_search($thread['category_id'], array_column($categories, 'id'))]['color'] ?? '#3498db'; ?>">
<a href="category.php?slug=<?php echo $thread['category_slug']; ?>">
<?php echo $thread['category_name']; ?>
</a>
</span>
<span class="author">
Started by 
<a href="profile.php?user=<?php echo $thread['author']; ?>">
<?php echo $thread['author']; ?>
</a>
</span>
<span class="time">
<?php echo timeAgo($thread['created_at']); ?>
</span>
<?php if ($thread['last_post_time']): ?>
<span class="last-post">
Last post <?php echo timeAgo($thread['last_post_time']); ?>
</span>
<?php endif; ?>
</div>
</div>
</div>
<?php endforeach; ?>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?page=<?php echo $page - 1; ?>" class="page-link">&laquo; Previous</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<a href="?page=<?php echo $i; ?>" 
class="page-link <?php echo $i == $page ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<a href="?page=<?php echo $page + 1; ?>" class="page-link">Next &raquo;</a>
<?php endif; ?>
</div>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<!-- Sidebar -->
<aside class="forum-sidebar">
<!-- Categories Widget -->
<div class="widget">
<h3 class="widget-title">Categories</h3>
<ul class="category-list">
<?php foreach ($categories as $cat): ?>
<li>
<a href="category.php?slug=<?php echo $cat['slug']; ?>">
<span class="cat-icon"><?php echo $cat['icon']; ?></span>
<span class="cat-name"><?php echo $cat['name']; ?></span>
<span class="cat-count"><?php echo $cat['thread_count']; ?></span>
</a>
</li>
<?php endforeach; ?>
</ul>
</div>
<!-- Statistics Widget -->
<div class="widget">
<h3 class="widget-title">Forum Statistics</h3>
<ul class="stats-list">
<li>Total Users: <strong><?php echo number_format($total_users); ?></strong></li>
<li>Total Discussions: <strong><?php echo number_format($total_threads_count); ?></strong></li>
<li>Total Posts: <strong><?php echo number_format($total_posts_count); ?></strong></li>
<li>Online Users: <strong><?php echo $online_users; ?></strong></li>
<li>Newest User: 
<strong>
<?php 
$newest = $pdo->query("SELECT username FROM users ORDER BY joined_at DESC LIMIT 1")->fetch();
echo $newest['username'];
?>
</strong>
</li>
</ul>
</div>
<!-- Active Users Widget -->
<div class="widget">
<h3 class="widget-title">Active Users</h3>
<div class="active-users">
<?php
$active = $pdo->query("
SELECT username, avatar 
FROM users 
WHERE last_seen > DATE_SUB(NOW(), INTERVAL 15 MINUTE)
ORDER BY last_seen DESC
LIMIT 10
")->fetchAll();
foreach ($active as $user): ?>
<a href="profile.php?user=<?php echo $user['username']; ?>" class="active-user" title="<?php echo $user['username']; ?>">
<img src="assets/images/avatars/<?php echo $user['avatar']; ?>" alt="<?php echo $user['username']; ?>">
</a>
<?php endforeach; ?>
</div>
</div>
</aside>
</div>
</main>
<!-- Footer -->
<footer class="forum-footer">
<div class="container">
<p>&copy; 2024 <?php echo FORUM_TITLE; ?>. All rights reserved.</p>
</div>
</footer>
<script src="assets/js/main.js"></script>
</body>
</html>

4. Thread View Page (thread.php)

<?php
require_once 'includes/config.php';
require_once 'vendor/parsedown/Parsedown.php';
$slug = $_GET['slug'] ?? '';
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * POSTS_PER_PAGE;
if (empty($slug)) {
redirect('index.php');
}
// Get thread details
$stmt = $pdo->prepare("
SELECT t.*, u.username as author, u.avatar as author_avatar, u.karma as author_karma,
c.name as category_name, c.slug as category_slug
FROM threads t
JOIN users u ON t.user_id = u.id
JOIN categories c ON t.category_id = c.id
WHERE t.slug = ? AND t.status = 'active'
");
$stmt->execute([$slug]);
$thread = $stmt->fetch();
if (!$thread) {
redirect('index.php');
}
// Increment view count
$pdo->prepare("UPDATE threads SET views = views + 1 WHERE id = ?")->execute([$thread['id']]);
// Get posts for this thread
$posts_stmt = $pdo->prepare("
SELECT p.*, u.username, u.avatar, u.karma, u.joined_at, u.posts_count,
(SELECT COUNT(*) FROM votes WHERE post_id = p.id AND vote_type = 'upvote') as upvotes,
(SELECT COUNT(*) FROM votes WHERE post_id = p.id AND vote_type = 'downvote') as downvotes
FROM posts p
JOIN users u ON p.user_id = u.id
WHERE p.thread_id = ? AND p.status = 'active'
ORDER BY p.created_at ASC
LIMIT ? OFFSET ?
");
$posts_stmt->execute([$thread['id'], POSTS_PER_PAGE, $offset]);
$posts = $posts_stmt->fetchAll();
// Get total posts count
$total_posts = $pdo->prepare("SELECT COUNT(*) FROM posts WHERE thread_id = ?");
$total_posts->execute([$thread['id']]);
$total_posts_count = $total_posts->fetchColumn();
$total_pages = ceil($total_posts_count / POSTS_PER_PAGE);
// Check if user has voted
$user_votes = [];
if (isLoggedIn()) {
$vote_stmt = $pdo->prepare("SELECT post_id, vote_type FROM votes WHERE user_id = ?");
$vote_stmt->execute([$_SESSION['user_id']]);
while ($vote = $vote_stmt->fetch()) {
$user_votes[$vote['post_id']] = $vote['vote_type'];
}
}
// Check if user is subscribed
$is_subscribed = false;
if (isLoggedIn()) {
$sub_stmt = $pdo->prepare("SELECT id FROM subscriptions WHERE user_id = ? AND thread_id = ?");
$sub_stmt->execute([$_SESSION['user_id'], $thread['id']]);
$is_subscribed = $sub_stmt->fetch() ? true : false;
}
// Handle reply submission
$reply_error = '';
$reply_success = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['submit_reply'])) {
if (!isLoggedIn()) {
$reply_error = 'You must be logged in to reply';
} elseif ($thread['is_locked']) {
$reply_error = 'This thread is locked';
} else {
$content = $_POST['content'];
if (empty($content)) {
$reply_error = 'Please enter a reply';
} else {
// Insert reply
$stmt = $pdo->prepare("
INSERT INTO posts (thread_id, user_id, content, is_first_post, ip_address, created_at)
VALUES (?, ?, ?, 0, ?, NOW())
");
$stmt->execute([
$thread['id'],
$_SESSION['user_id'],
$content,
$_SERVER['REMOTE_ADDR']
]);
$post_id = $pdo->lastInsertId();
// Update thread last post info
$pdo->prepare("
UPDATE threads 
SET replies_count = replies_count + 1, 
last_post_id = ?, 
last_post_time = NOW() 
WHERE id = ?
")->execute([$post_id, $thread['id']]);
// Update user post count
$pdo->prepare("UPDATE users SET posts_count = posts_count + 1 WHERE id = ?")
->execute([$_SESSION['user_id']]);
// Notify thread author
if ($thread['user_id'] != $_SESSION['user_id']) {
sendNotification(
$thread['user_id'],
'reply',
'New reply to your thread',
$_SESSION['user_name'] . ' replied to your thread "' . $thread['title'] . '"',
'thread.php?slug=' . $thread['slug'] . '#post-' . $post_id
);
}
// Notify subscribers
$subscribers = $pdo->prepare("SELECT user_id FROM subscriptions WHERE thread_id = ? AND user_id != ?");
$subscribers->execute([$thread['id'], $_SESSION['user_id']]);
while ($sub = $subscribers->fetch()) {
sendNotification(
$sub['user_id'],
'reply',
'New reply in subscribed thread',
$_SESSION['user_name'] . ' replied to "' . $thread['title'] . '"',
'thread.php?slug=' . $thread['slug'] . '#post-' . $post_id
);
}
$reply_success = 'Your reply has been posted';
// Redirect to avoid resubmission
redirect('thread.php?slug=' . $thread['slug'] . '&page=' . $total_pages . '#post-' . $post_id);
}
}
}
// Initialize Markdown parser
$Parsedown = new Parsedown();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $thread['title']; ?> - <?php echo FORUM_TITLE; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header -->
<?php include 'includes/header.php'; ?>
<main class="container">
<!-- Breadcrumb -->
<div class="breadcrumb">
<a href="index.php">Home</a> &raquo;
<a href="category.php?slug=<?php echo $thread['category_slug']; ?>">
<?php echo $thread['category_name']; ?>
</a> &raquo;
<span><?php echo $thread['title']; ?></span>
</div>
<!-- Thread Header -->
<div class="thread-header">
<h1 class="thread-title">
<?php if ($thread['is_pinned']): ?>📌<?php endif; ?>
<?php if ($thread['is_locked']): ?>🔒<?php endif; ?>
<?php echo $thread['title']; ?>
</h1>
<div class="thread-actions">
<?php if (isLoggedIn()): ?>
<button id="subscribe-btn" class="btn <?php echo $is_subscribed ? 'btn-success' : ''; ?>" 
onclick="toggleSubscribe(<?php echo $thread['id']; ?>)">
<?php echo $is_subscribed ? '✓ Subscribed' : '🔔 Subscribe'; ?>
</button>
<?php if (isModerator() || $thread['user_id'] == $_SESSION['user_id']): ?>
<a href="edit-thread.php?id=<?php echo $thread['id']; ?>" class="btn">Edit</a>
<?php endif; ?>
<?php if (isModerator()): ?>
<button class="btn" onclick="toggleLock(<?php echo $thread['id']; ?>)">
<?php echo $thread['is_locked'] ? 'Unlock' : 'Lock'; ?>
</button>
<button class="btn" onclick="togglePin(<?php echo $thread['id']; ?>)">
<?php echo $thread['is_pinned'] ? 'Unpin' : 'Pin'; ?>
</button>
<?php endif; ?>
<?php endif; ?>
</div>
</div>
<!-- Posts List -->
<div class="posts-list">
<?php foreach ($posts as $index => $post): ?>
<div class="post" id="post-<?php echo $post['id']; ?>">
<div class="post-sidebar">
<div class="post-author">
<img src="assets/images/avatars/<?php echo $post['avatar']; ?>" 
alt="<?php echo $post['username']; ?>" class="avatar-large">
<div class="author-info">
<a href="profile.php?user=<?php echo $post['username']; ?>" class="author-name">
<?php echo $post['username']; ?>
</a>
<span class="author-karma">⚡ <?php echo $post['karma']; ?></span>
<span class="author-title">
<?php
if ($post['username'] == $thread['author']) {
echo '<span class="op-badge">OP</span>';
}
?>
</span>
</div>
<div class="author-stats">
<span>Joined: <?php echo date('M Y', strtotime($post['joined_at'])); ?></span>
<span>Posts: <?php echo $post['posts_count']; ?></span>
</div>
</div>
</div>
<div class="post-content">
<div class="post-meta">
<span class="post-date"><?php echo timeAgo($post['created_at']); ?></span>
<?php if ($post['is_best_answer']): ?>
<span class="best-answer">⭐ Best Answer</span>
<?php endif; ?>
<?php if ($index === 0): ?>
<span class="first-post">First Post</span>
<?php endif; ?>
</div>
<div class="post-body">
<?php echo $Parsedown->text($post['content']); ?>
</div>
<div class="post-footer">
<div class="post-votes">
<button class="vote-btn upvote <?php echo ($user_votes[$post['id']] ?? '') == 'upvote' ? 'voted' : ''; ?>" 
onclick="vote(<?php echo $post['id']; ?>, 'upvote')">
▲ <span class="vote-count"><?php echo $post['upvotes']; ?></span>
</button>
<button class="vote-btn downvote <?php echo ($user_votes[$post['id']] ?? '') == 'downvote' ? 'voted' : ''; ?>" 
onclick="vote(<?php echo $post['id']; ?>, 'downvote')">
▼ <span class="vote-count"><?php echo $post['downvotes']; ?></span>
</button>
</div>
<div class="post-actions">
<a href="#reply-form" onclick="quotePost(<?php echo $post['id']; ?>, '<?php echo $post['username']; ?>')" class="action-link">Quote</a>
<?php if (isLoggedIn() && !$post['is_best_answer'] && $thread['user_id'] == $_SESSION['user_id'] && $index > 0): ?>
<a href="#" onclick="markBestAnswer(<?php echo $post['id']; ?>)" class="action-link">Mark as Best Answer</a>
<?php endif; ?>
<?php if (isLoggedIn() && $_SESSION['user_id'] != $post['user_id']): ?>
<a href="#" onclick="reportPost(<?php echo $post['id']; ?>)" class="action-link">Report</a>
<?php endif; ?>
<?php if (isModerator() || $_SESSION['user_id'] == $post['user_id']): ?>
<a href="edit-post.php?id=<?php echo $post['id']; ?>" class="action-link">Edit</a>
<a href="#" onclick="deletePost(<?php echo $post['id']; ?>)" class="action-link">Delete</a>
<?php endif; ?>
</div>
</div>
<?php if ($post['updated_at']): ?>
<div class="post-edited">
Last edited <?php echo timeAgo($post['updated_at']); ?>
</div>
<?php endif; ?>
</div>
</div>
<?php endforeach; ?>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="pagination">
<?php if ($page > 1): ?>
<a href="?slug=<?php echo $slug; ?>&page=<?php echo $page - 1; ?>" class="page-link">&laquo; Previous</a>
<?php endif; ?>
<?php for ($i = 1; $i <= $total_pages; $i++): ?>
<a href="?slug=<?php echo $slug; ?>&page=<?php echo $i; ?>" 
class="page-link <?php echo $i == $page ? 'active' : ''; ?>">
<?php echo $i; ?>
</a>
<?php endfor; ?>
<?php if ($page < $total_pages): ?>
<a href="?slug=<?php echo $slug; ?>&page=<?php echo $page + 1; ?>" class="page-link">Next &raquo;</a>
<?php endif; ?>
</div>
<?php endif; ?>
</div>
<!-- Reply Form -->
<?php if (!isLoggedIn()): ?>
<div class="login-to-reply">
<p>Please <a href="login.php">login</a> or <a href="register.php">register</a> to reply to this thread.</p>
</div>
<?php elseif ($thread['is_locked']): ?>
<div class="thread-locked">
<p>This thread has been locked. No further replies can be posted.</p>
</div>
<?php else: ?>
<div class="reply-form-container" id="reply-form">
<h3>Post a Reply</h3>
<?php if ($reply_error): ?>
<div class="alert alert-error"><?php echo $reply_error; ?></div>
<?php endif; ?>
<?php if ($reply_success): ?>
<div class="alert alert-success"><?php echo $reply_success; ?></div>
<?php endif; ?>
<form method="POST" action="" class="reply-form">
<div class="form-group">
<textarea id="reply-content" name="content" rows="8" placeholder="Write your reply... (Markdown supported)"></textarea>
<div class="markdown-preview" id="preview"></div>
</div>
<div class="form-actions">
<button type="button" class="btn" onclick="togglePreview()">Preview</button>
<button type="submit" name="submit_reply" class="btn btn-primary">Post Reply</button>
</div>
</form>
</div>
<?php endif; ?>
</main>
<!-- Footer -->
<?php include 'includes/footer.php'; ?>
<script src="assets/js/markdown.js"></script>
<script src="assets/js/voting.js"></script>
<script>
function vote(postId, type) {
<?php if (!isLoggedIn()): ?>
alert('Please login to vote');
return;
<?php endif; ?>
fetch('api/vote.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `post_id=${postId}&type=${type}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
} else {
alert(data.message);
}
});
}
function toggleSubscribe(threadId) {
fetch('api/subscribe.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `thread_id=${threadId}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
const btn = document.getElementById('subscribe-btn');
if (data.subscribed) {
btn.textContent = '✓ Subscribed';
btn.classList.add('btn-success');
} else {
btn.textContent = '🔔 Subscribe';
btn.classList.remove('btn-success');
}
}
});
}
function quotePost(postId, username) {
// Get post content (simplified - in production you'd fetch this)
const textarea = document.getElementById('reply-content');
textarea.value += `> *Quoting ${username}*\n\n`;
textarea.focus();
}
function togglePreview() {
const preview = document.getElementById('preview');
const content = document.getElementById('reply-content').value;
if (preview.style.display === 'none') {
// Convert markdown to HTML (using simple markdown library)
preview.innerHTML = marked.parse(content);
preview.style.display = 'block';
} else {
preview.style.display = 'none';
}
}
</script>
</body>
</html>

5. Create Thread Page (create-thread.php)

<?php
require_once 'includes/config.php';
require_once 'includes/auth.php';
Auth::checkLogin();
// Get categories
$categories = $pdo->query("SELECT * FROM categories WHERE status = 'active' ORDER BY name")->fetchAll();
$errors = [];
$form_data = [
'title' => '',
'category_id' => '',
'content' => ''
];
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$form_data['title'] = sanitize($_POST['title']);
$form_data['category_id'] = (int)$_POST['category_id'];
$form_data['content'] = $_POST['content'];
// Validate
if (empty($form_data['title'])) {
$errors['title'] = 'Title is required';
} elseif (strlen($form_data['title']) < 5) {
$errors['title'] = 'Title must be at least 5 characters';
}
if (empty($form_data['category_id'])) {
$errors['category_id'] = 'Please select a category';
}
if (empty($form_data['content'])) {
$errors['content'] = 'Content is required';
} elseif (strlen($form_data['content']) < 10) {
$errors['content'] = 'Content must be at least 10 characters';
}
if (empty($errors)) {
try {
$pdo->beginTransaction();
// Create slug
$slug = createSlug($form_data['title']);
// Check if slug exists and make it unique
$check = $pdo->prepare("SELECT id FROM threads WHERE slug = ?");
$check->execute([$slug]);
if ($check->fetch()) {
$slug .= '-' . uniqid();
}
// Insert thread
$stmt = $pdo->prepare("
INSERT INTO threads (title, slug, category_id, user_id, created_at)
VALUES (?, ?, ?, ?, NOW())
");
$stmt->execute([
$form_data['title'],
$slug,
$form_data['category_id'],
$_SESSION['user_id']
]);
$thread_id = $pdo->lastInsertId();
// Insert first post
$stmt = $pdo->prepare("
INSERT INTO posts (thread_id, user_id, content, is_first_post, ip_address, created_at)
VALUES (?, ?, ?, 1, ?, NOW())
");
$stmt->execute([
$thread_id,
$_SESSION['user_id'],
$form_data['content'],
$_SERVER['REMOTE_ADDR']
]);
$post_id = $pdo->lastInsertId();
// Update thread with last post info
$pdo->prepare("
UPDATE threads 
SET last_post_id = ?, last_post_time = NOW() 
WHERE id = ?
")->execute([$post_id, $thread_id]);
// Update category thread count
$pdo->prepare("
UPDATE categories 
SET threads_count = threads_count + 1, 
posts_count = posts_count + 1,
last_post_id = ?,
last_post_time = NOW()
WHERE id = ?
")->execute([$post_id, $form_data['category_id']]);
// Update user post count
$pdo->prepare("UPDATE users SET posts_count = posts_count + 1 WHERE id = ?")
->execute([$_SESSION['user_id']]);
$pdo->commit();
// Redirect to the new thread
redirect('thread.php?slug=' . $slug);
} catch (Exception $e) {
$pdo->rollBack();
$errors['general'] = 'Failed to create thread: ' . $e->getMessage();
}
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Create New Discussion - <?php echo FORUM_TITLE; ?></title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<!-- Header -->
<?php include 'includes/header.php'; ?>
<main class="container">
<div class="create-thread-container">
<h1>Start a New Discussion</h1>
<?php if (!empty($errors['general'])): ?>
<div class="alert alert-error"><?php echo $errors['general']; ?></div>
<?php endif; ?>
<form method="POST" action="" class="create-thread-form">
<div class="form-group">
<label for="title">Discussion Title</label>
<input type="text" id="title" name="title" 
value="<?php echo htmlspecialchars($form_data['title']); ?>"
placeholder="Enter a descriptive title" required>
<?php if (isset($errors['title'])): ?>
<span class="error"><?php echo $errors['title']; ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label for="category">Category</label>
<select id="category" name="category_id" required>
<option value="">Select a category</option>
<?php foreach ($categories as $cat): ?>
<option value="<?php echo $cat['id']; ?>" 
<?php echo $form_data['category_id'] == $cat['id'] ? 'selected' : ''; ?>>
<?php echo $cat['icon']; ?> <?php echo $cat['name']; ?>
</option>
<?php endforeach; ?>
</select>
<?php if (isset($errors['category_id'])): ?>
<span class="error"><?php echo $errors['category_id']; ?></span>
<?php endif; ?>
</div>
<div class="form-group">
<label for="content">Content</label>
<textarea id="content" name="content" rows="12" 
placeholder="Write your discussion content here... (Markdown supported)"><?php echo htmlspecialchars($form_data['content']); ?></textarea>
<?php if (isset($errors['content'])): ?>
<span class="error"><?php echo $errors['content']; ?></span>
<?php endif; ?>
<div class="markdown-help">
<p>💡 <strong>Markdown supported:</strong> **bold**, *italic*, [links](url), `code`, lists, etc.</p>
</div>
</div>
<div class="form-actions">
<button type="submit" class="btn btn-primary btn-large">Create Discussion</button>
<a href="index.php" class="btn btn-large">Cancel</a>
</div>
</form>
</div>
</main>
<!-- Footer -->
<?php include 'includes/footer.php'; ?>
<script src="assets/js/markdown.js"></script>
</body>
</html>

6. Admin Dashboard (admin/dashboard.php)

<?php
require_once '../includes/config.php';
require_once '../includes/auth.php';
Auth::checkModerator();
// Get statistics
$totalUsers = $pdo->query("SELECT COUNT(*) FROM users")->fetchColumn();
$totalThreads = $pdo->query("SELECT COUNT(*) FROM threads")->fetchColumn();
$totalPosts = $pdo->query("SELECT COUNT(*) FROM posts")->fetchColumn();
$pendingReports = $pdo->query("SELECT COUNT(*) FROM reports WHERE status = 'pending'")->fetchColumn();
// Get recent users
$recentUsers = $pdo->query("
SELECT * FROM users 
ORDER BY joined_at DESC 
LIMIT 5
")->fetchAll();
// Get recent reports
$recentReports = $pdo->prepare("
SELECT r.*, p.thread_id, p.content as post_content,
u.username as reported_by_name,
pu.username as post_author
FROM reports r
JOIN posts p ON r.post_id = p.id
JOIN users u ON r.reported_by = u.id
JOIN users pu ON p.user_id = pu.id
WHERE r.status = 'pending'
ORDER BY r.created_at DESC
LIMIT 10
");
$recentReports->execute();
$reports = $recentReports->fetchAll();
// Get popular threads
$popularThreads = $pdo->query("
SELECT t.*, u.username, c.name as category_name,
(SELECT COUNT(*) FROM posts WHERE thread_id = t.id) as reply_count
FROM threads t
JOIN users u ON t.user_id = u.id
JOIN categories c ON t.category_id = c.id
ORDER BY t.views DESC
LIMIT 5
")->fetchAll();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Admin Dashboard - <?php echo FORUM_TITLE; ?></title>
<link rel="stylesheet" href="../assets/css/admin.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
</head>
<body>
<div class="admin-wrapper">
<!-- Sidebar -->
<aside class="sidebar">
<div class="sidebar-header">
<h2>Forum Admin</h2>
<p>Welcome, <?php echo $_SESSION['user_name']; ?></p>
</div>
<nav class="sidebar-nav">
<ul>
<li><a href="dashboard.php" class="active">Dashboard</a></li>
<li><a href="categories.php">Categories</a></li>
<li><a href="users.php">Users</a></li>
<li><a href="posts.php">Posts</a></li>
<li><a href="reports.php">Reports <?php if ($pendingReports > 0): ?><span class="badge"><?php echo $pendingReports; ?></span><?php endif; ?></a></li>
<li><a href="settings.php">Settings</a></li>
<li><a href="logout.php">Logout</a></li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="admin-main">
<div class="container">
<h1>Dashboard</h1>
<!-- Stats Cards -->
<div class="stats-grid">
<div class="stat-card">
<div class="stat-icon">👥</div>
<div class="stat-details">
<h3><?php echo number_format($totalUsers); ?></h3>
<p>Total Users</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">📝</div>
<div class="stat-details">
<h3><?php echo number_format($totalThreads); ?></h3>
<p>Discussions</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">💬</div>
<div class="stat-details">
<h3><?php echo number_format($totalPosts); ?></h3>
<p>Posts</p>
</div>
</div>
<div class="stat-card">
<div class="stat-icon">🚩</div>
<div class="stat-details">
<h3><?php echo $pendingReports; ?></h3>
<p>Pending Reports</p>
</div>
</div>
</div>
<!-- Quick Actions -->
<div class="quick-actions">
<a href="categories.php" class="action-card">
<span class="action-icon">📁</span>
<span>Manage Categories</span>
</a>
<a href="users.php" class="action-card">
<span class="action-icon">👥</span>
<span>Manage Users</span>
</a>
<a href="reports.php" class="action-card">
<span class="action-icon">🚩</span>
<span>Review Reports</span>
<?php if ($pendingReports > 0): ?>
<span class="badge"><?php echo $pendingReports; ?></span>
<?php endif; ?>
</a>
<a href="settings.php" class="action-card">
<span class="action-icon">⚙️</span>
<span>Forum Settings</span>
</a>
</div>
<div class="dashboard-grid">
<!-- Recent Reports -->
<div class="card">
<div class="card-header">
<h3>Recent Reports</h3>
<a href="reports.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<?php if (empty($reports)): ?>
<p>No pending reports</p>
<?php else: ?>
<table class="data-table">
<thead>
<tr>
<th>Reported Post</th>
<th>Author</th>
<th>Reported By</th>
<th>Reason</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($reports as $report): ?>
<tr>
<td><?php echo substr($report['post_content'], 0, 50); ?>...</td>
<td><?php echo $report['post_author']; ?></td>
<td><?php echo $report['reported_by_name']; ?></td>
<td><?php echo $report['reason']; ?></td>
<td>
<a href="view-post.php?id=<?php echo $report['post_id']; ?>" class="btn-small">View</a>
<a href="?dismiss=<?php echo $report['id']; ?>" class="btn-small">Dismiss</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
</div>
<!-- Popular Threads -->
<div class="card">
<div class="card-header">
<h3>Popular Threads</h3>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>Title</th>
<th>Category</th>
<th>Author</th>
<th>Views</th>
<th>Replies</th>
</tr>
</thead>
<tbody>
<?php foreach ($popularThreads as $thread): ?>
<tr>
<td>
<a href="../thread.php?slug=<?php echo $thread['slug']; ?>" target="_blank">
<?php echo $thread['title']; ?>
</a>
</td>
<td><?php echo $thread['category_name']; ?></td>
<td><?php echo $thread['username']; ?></td>
<td><?php echo number_format($thread['views']); ?></td>
<td><?php echo $thread['reply_count']; ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
<!-- Recent Users -->
<div class="card">
<div class="card-header">
<h3>Recent Users</h3>
<a href="users.php" class="btn btn-sm">View All</a>
</div>
<div class="card-body">
<table class="data-table">
<thead>
<tr>
<th>Username</th>
<th>Email</th>
<th>Joined</th>
<th>Status</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<?php foreach ($recentUsers as $user): ?>
<tr>
<td><?php echo $user['username']; ?></td>
<td><?php echo $user['email']; ?></td>
<td><?php echo date('M d, Y', strtotime($user['joined_at'])); ?></td>
<td>
<span class="badge badge-<?php echo $user['status']; ?>">
<?php echo $user['status']; ?>
</span>
</td>
<td>
<a href="edit-user.php?id=<?php echo $user['id']; ?>" class="btn-small">Edit</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
</div>
</div>
</div>
</main>
</div>
<script src="../assets/js/admin.js"></script>
</body>
</html>

7. CSS Styling (assets/css/style.css)

/* Discussion Forum Styles */
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--danger-color: #e74c3c;
--warning-color: #f39c12;
--dark-color: #2c3e50;
--light-color: #ecf0f1;
--text-color: #333;
--border-color: #ddd;
--success-color: #27ae60;
--info-color: #3498db;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
line-height: 1.6;
color: var(--text-color);
background: #f4f6f9;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header Styles */
.forum-header {
background: #fff;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 20px;
}
.header-top {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid var(--border-color);
}
.logo h1 {
font-size: 1.8rem;
}
.logo a {
text-decoration: none;
color: var(--dark-color);
}
.search-bar {
flex: 0 1 400px;
}
.search-bar form {
display: flex;
}
.search-bar input {
flex: 1;
padding: 8px 12px;
border: 1px solid var(--border-color);
border-radius: 4px 0 0 4px;
font-size: 14px;
}
.search-bar button {
padding: 8px 15px;
background: var(--primary-color);
color: #fff;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
}
.user-menu {
display: flex;
align-items: center;
gap: 10px;
}
.user-dropdown {
position: relative;
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
padding: 5px 10px;
border-radius: 4px;
}
.user-dropdown:hover {
background: var(--light-color);
}
.avatar-small {
width: 32px;
height: 32px;
border-radius: 50%;
object-fit: cover;
}
.karma-badge {
background: var(--warning-color);
color: #fff;
padding: 2px 6px;
border-radius: 3px;
font-size: 12px;
}
.notification-badge {
background: var(--danger-color);
color: #fff;
padding: 2px 6px;
border-radius: 10px;
font-size: 11px;
position: absolute;
top: -5px;
right: -5px;
}
.dropdown-menu {
position: absolute;
top: 100%;
right: 0;
background: #fff;
border: 1px solid var(--border-color);
border-radius: 4px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
display: none;
z-index: 1000;
min-width: 150px;
}
.user-dropdown:hover .dropdown-menu {
display: block;
}
.dropdown-menu a {
display: block;
padding: 10px 15px;
color: var(--text-color);
text-decoration: none;
border-bottom: 1px solid var(--border-color);
}
.dropdown-menu a:hover {
background: var(--light-color);
}
.main-nav {
padding: 10px 0;
}
.main-nav ul {
list-style: none;
display: flex;
gap: 20px;
}
.main-nav a {
text-decoration: none;
color: var(--text-color);
font-weight: 500;
padding: 5px 10px;
border-radius: 4px;
}
.main-nav a:hover,
.main-nav a.active {
background: var(--light-color);
color: var(--primary-color);
}
.btn-create {
background: var(--success-color);
color: #fff !important;
}
.btn-create:hover {
background: #219a52 !important;
color: #fff !important;
}
/* Forum Layout */
.forum-layout {
display: grid;
grid-template-columns: 1fr 300px;
gap: 20px;
}
.forum-stats {
background: #fff;
padding: 15px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 20px;
display: flex;
gap: 30px;
justify-content: center;
}
.forum-stats span {
display: flex;
align-items: center;
gap: 5px;
color: #666;
}
/* Thread List */
.thread-list {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
overflow: hidden;
}
.thread-list-header {
padding: 15px 20px;
background: var(--light-color);
border-bottom: 1px solid var(--border-color);
display: flex;
justify-content: space-between;
align-items: center;
}
.thread-list-header h2 {
font-size: 1.3rem;
color: var(--dark-color);
}
.thread-item {
display: flex;
padding: 15px 20px;
border-bottom: 1px solid var(--border-color);
transition: background 0.3s;
}
.thread-item:hover {
background: #f9f9f9;
}
.thread-stats {
display: flex;
gap: 20px;
min-width: 120px;
}
.stat {
text-align: center;
}
.stat-value {
display: block;
font-size: 1.2rem;
font-weight: bold;
color: var(--dark-color);
}
.stat-label {
font-size: 0.8rem;
color: #999;
}
.thread-content {
flex: 1;
}
.thread-content h3 {
font-size: 1.1rem;
margin-bottom: 8px;
}
.thread-content h3 a {
color: var(--dark-color);
text-decoration: none;
}
.thread-content h3 a:hover {
color: var(--primary-color);
}
.thread-meta {
display: flex;
gap: 15px;
font-size: 0.9rem;
color: #666;
}
.category-badge a {
color: #fff;
text-decoration: none;
}
.category-badge a:hover {
text-decoration: underline;
}
.author a {
color: var(--primary-color);
text-decoration: none;
font-weight: 500;
}
.time, .last-post {
color: #999;
}
/* Thread View */
.thread-header {
background: #fff;
padding: 20px;
border-radius: 8px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
display: flex;
justify-content: space-between;
align-items: center;
}
.thread-title {
font-size: 2rem;
color: var(--dark-color);
}
.thread-actions {
display: flex;
gap: 10px;
}
.posts-list {
margin-bottom: 30px;
}
.post {
background: #fff;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
margin-bottom: 20px;
display: flex;
overflow: hidden;
}
.post-sidebar {
width: 200px;
background: #f9f9f9;
padding: 20px;
border-right: 1px solid var(--border-color);
}
.post-author {
text-align: center;
}
.avatar-large {
width: 100px;
height: 100px;
border-radius: 50%;
object-fit: cover;
margin-bottom: 10px;
}
.author-info {
margin-bottom: 10px;
}
.author-name {
display: block;
font-size: 1.1rem;
font-weight: bold;
color: var(--dark-color);
text-decoration: none;
margin-bottom: 5px;
}
.author-name:hover {
color: var(--primary-color);
}
.author-karma {
display: inline-block;
background: var(--warning-color);
color: #fff;
padding: 2px 8px;
border-radius: 3px;
font-size: 0.9rem;
margin-bottom: 5px;
}
.op-badge {
display: inline-block;
background: var(--primary-color);
color: #fff;
padding: 2px 8px;
border-radius: 3px;
font-size: 0.8rem;
}
.author-stats {
font-size: 0.8rem;
color: #999;
}
.author-stats span {
display: block;
margin-bottom: 3px;
}
.post-content {
flex: 1;
padding: 20px;
}
.post-meta {
display: flex;
gap: 15px;
margin-bottom: 15px;
font-size: 0.9rem;
color: #999;
padding-bottom: 10px;
border-bottom: 1px solid var(--border-color);
}
.best-answer {
background: var(--success-color);
color: #fff;
padding: 2px 8px;
border-radius: 3px;
font-size: 0.8rem;
}
.first-post {
background: var(--info-color);
color: #fff;
padding: 2px 8px;
border-radius: 3px;
font-size: 0.8rem;
}
.post-body {
margin-bottom: 20px;
line-height: 1.7;
}
.post-body p {
margin-bottom: 15px;
}
.post-body code {
background: #f4f4f4;
padding: 2px 5px;
border-radius: 3px;
font-family: 'Courier New', monospace;
}
.post-body pre {
background: #f4f4f4;
padding: 15px;
border-radius: 5px;
overflow-x: auto;
margin: 15px 0;
}
.post-body blockquote {
border-left: 4px solid var(--primary-color);
padding-left: 15px;
margin: 15px 0;
color: #666;
font-style: italic;
}
.post-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 15px;
border-top: 1px solid var(--border-color);
}
.post-votes {
display: flex;
gap: 10px;
}
.vote-btn {
display: flex;
align-items: center;
gap: 5px;
padding: 5px 10px;
border: 1px solid var(--border-color);
border-radius: 4px;
background: #fff;
cursor: pointer;
color: #666;
}
.vote-btn.voted {
background: var(--primary-color);
color: #fff;
border-color: var(--primary-color);
}
.vote-btn.upvote.voted { background: var(--success-color); }
.vote-btn.downvote.voted { background: var(--danger-color); }
.post-actions {
display: flex;
gap: 10px;
}
.action-link {
color: #999;
text-decoration: none;
font-size: 0.9rem;
}
.action-link:hover {
color: var(--primary-color);
}
.post-edited {
margin-top: 15px;
font-size: 0.8rem;
color: #999;
font-style: italic;
}
/* Reply Form */
.reply-form-container {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.reply-form textarea {
width: 100%;
padding: 15px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 1rem;
font-family: inherit;
}
.markdown-preview {
display: none;
margin-top: 15px;
padding: 15px;
background: #f9f9f9;
border-radius: 4px;
border: 1px solid var(--border-color);
}
/* Categories Sidebar */
.widget {
background: #fff;
border-radius: 8px;
padding: 20px;
margin-bottom: 20px;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}
.widget-title {
font-size: 1.2rem;
color: var(--dark-color);
margin-bottom: 15px;
padding-bottom: 8px;
border-bottom: 2px solid var(--primary-color);
}
.category-list {
list-style: none;
}
.category-list li {
margin-bottom: 8px;
}
.category-list a {
display: flex;
align-items: center;
padding: 8px;
text-decoration: none;
color: var(--text-color);
border-radius: 4px;
transition: background 0.3s;
}
.category-list a:hover {
background: var(--light-color);
}
.cat-icon {
width: 30px;
font-size: 1.2rem;
}
.cat-name {
flex: 1;
}
.cat-count {
background: var(--light-color);
padding: 2px 6px;
border-radius: 3px;
font-size: 0.8rem;
color: #666;
}
.stats-list {
list-style: none;
}
.stats-list li {
padding: 8px 0;
border-bottom: 1px solid var(--border-color);
}
.stats-list li:last-child {
border-bottom: none;
}
.active-users {
display: flex;
flex-wrap: wrap;
gap: 10px;
}
.active-user img {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
}
/* Create Thread Form */
.create-thread-container {
max-width: 800px;
margin: 0 auto;
background: #fff;
padding: 30px;
border-radius: 8px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
.create-thread-container h1 {
margin-bottom: 30px;
color: var(--dark-color);
}
.create-thread-form .form-group {
margin-bottom: 20px;
}
.create-thread-form label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: var(--dark-color);
}
.create-thread-form input[type="text"],
.create-thread-form select,
.create-thread-form textarea {
width: 100%;
padding: 12px;
border: 1px solid var(--border-color);
border-radius: 4px;
font-size: 1rem;
}
.create-thread-form textarea {
font-family: inherit;
}
.markdown-help {
margin-top: 5px;
font-size: 0.9rem;
color: #999;
}
.form-actions {
display: flex;
gap: 15px;
justify-content: flex-end;
}
/* Pagination */
.pagination {
display: flex;
justify-content: center;
gap: 5px;
margin: 30px 0;
}
.page-link {
padding: 8px 15px;
background: #fff;
color: var(--text-color);
text-decoration: none;
border: 1px solid var(--border-color);
border-radius: 4px;
transition: all 0.3s;
}
.page-link:hover,
.page-link.active {
background: var(--primary-color);
color: #fff;
border-color: var(--primary-color);
}
/* Breadcrumb */
.breadcrumb {
margin-bottom: 20px;
font-size: 0.9rem;
color: #999;
}
.breadcrumb a {
color: var(--primary-color);
text-decoration: none;
}
.breadcrumb a:hover {
text-decoration: underline;
}
/* Buttons */
.btn {
display: inline-block;
padding: 8px 15px;
background: var(--light-color);
color: var(--text-color);
text-decoration: none;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
font-size: 0.9rem;
}
.btn:hover {
background: #d5dbdb;
}
.btn-primary {
background: var(--primary-color);
color: #fff;
}
.btn-primary:hover {
background: #2980b9;
}
.btn-success {
background: var(--success-color);
color: #fff;
}
.btn-success:hover {
background: #219a52;
}
.btn-danger {
background: var(--danger-color);
color: #fff;
}
.btn-danger:hover {
background: #c0392b;
}
.btn-small {
padding: 5px 10px;
font-size: 0.8rem;
}
.btn-large {
padding: 12px 25px;
font-size: 1rem;
}
/* Alerts */
.alert {
padding: 15px;
border-radius: 4px;
margin-bottom: 20px;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
/* Footer */
.forum-footer {
background: var(--dark-color);
color: #fff;
text-align: center;
padding: 20px 0;
margin-top: 40px;
}
/* Responsive */
@media (max-width: 768px) {
.forum-layout {
grid-template-columns: 1fr;
}
.header-top {
flex-direction: column;
gap: 15px;
}
.search-bar {
width: 100%;
}
.post {
flex-direction: column;
}
.post-sidebar {
width: 100%;
border-right: none;
border-bottom: 1px solid var(--border-color);
}
.thread-meta {
flex-wrap: wrap;
}
.thread-stats {
min-width: auto;
}
}

📝 How to Use This Project (Step-by-Step Guide)

Step 1: Install Local Server

  1. Download and install XAMPP from apachefriends.org
  2. Launch XAMPP Control Panel
  3. Start Apache and MySQL services

Step 2: Create Project Folder

  1. Navigate to C:\xampp\htdocs\ (Windows) or /Applications/XAMPP/htdocs/ (Mac)
  2. Create a new folder named discussion-forum

Step 3: Set Up Database

  1. Open browser and go to http://localhost/phpmyadmin
  2. Click on "SQL" tab
  3. Copy the entire SQL from database/forum.sql
  4. Paste and click "Go" to create database and tables

Step 4: Create Password Hash

Create a file named hash.php in project root:

<?php
echo "admin123 hash: " . password_hash('admin123', PASSWORD_DEFAULT) . "\n";
echo "mod123 hash: " . password_hash('mod123', PASSWORD_DEFAULT) . "\n";
echo "member123 hash: " . password_hash('member123', PASSWORD_DEFAULT);
?>

Run it: http://localhost/discussion-forum/hash.php
Copy the hashes and update them in the SQL insert statements for the users.

Step 5: Configure Database Connection

Open includes/config.php and verify:

define('DB_HOST', 'localhost');
define('DB_NAME', 'discussion_forum');
define('DB_USER', 'root');
define('DB_PASS', '');

Step 6: Create Required Directories

Create these folders and set proper permissions:

  • assets/images/avatars/ - for user avatars
  • assets/images/uploads/ - for file uploads (if implementing)

Step 7: Download Third-Party Libraries

  1. Download Parsedown from github.com/erusev/parsedown and place in vendor/parsedown/

Step 8: Test the Application

Public Side:

  • Open: http://localhost/discussion-forum/
  • Browse categories and threads
  • Register a new account
  • Create a new discussion
  • Post replies
  • Vote on posts

Admin Side:

  • Open: http://localhost/discussion-forum/admin/login.php
  • Login with: Username: admin, Password: admin123
  • Or login as moderator: Username: moderator, Password: mod123

Step 9: Add Sample Content

  1. Login to admin panel
  2. Go to Categories → Add categories if needed
  3. Go to the main site and create some test discussions
  4. Try posting replies and voting
  5. Test the reporting system

Step 10: Customize Forum Settings

  1. Go to Settings in admin panel
  2. Update forum title, description
  3. Configure posting rules and karma settings
  4. Set email notification preferences

🎯 Features Summary

User Features:

  • ✅ User registration and login
  • ✅ Create new discussion threads
  • ✅ Post replies with Markdown support
  • ✅ Vote on posts (upvote/downvote)
  • ✅ Karma system for user reputation
  • ✅ Subscribe to threads for notifications
  • ✅ Bookmark favorite threads
  • ✅ User profiles with post history
  • ✅ Search functionality
  • ✅ Report inappropriate content

Moderator Features:

  • ✅ Dashboard with statistics
  • ✅ Category management
  • ✅ User management (ban, promote)
  • ✅ Post moderation (edit, delete, feature)
  • ✅ Report review system
  • ✅ Thread locking/pinning
  • ✅ Mark best answers

Technical Features:

  • ✅ Secure password hashing
  • ✅ Session management
  • ✅ SQL injection prevention
  • ✅ XSS protection
  • ✅ SEO-friendly URLs (slugs)
  • ✅ Pagination
  • ✅ Markdown support
  • ✅ Voting system with AJAX
  • ✅ Notification system
  • ✅ Responsive design

🚀 Future Enhancements

  1. Private Messages: Direct messaging between users
  2. User Groups: Create private forums for groups
  3. File Attachments: Upload images and files
  4. Email Notifications: Real-time email alerts
  5. Social Login: Login with Google/Facebook
  6. API: REST API for mobile apps
  7. Rich Text Editor: WYSIWYG editor option
  8. User Badges: Achievement system
  9. Poll Creation: Add polls to threads
  10. Mentions: @username mentions with notifications
  11. Thread Tags: Tag system for better organization
  12. Search Filters: Advanced search options
  13. Dark Mode: Theme switcher
  14. Spam Protection: CAPTCHA integration
  15. Analytics: Detailed usage statistics
  16. Export Data: Export threads/posts to PDF
  17. Backup System: Automated database backups
  18. Multi-language: Internationalization support
  19. SEO Tools: Meta tags, sitemap generator
  20. Mobile App: Native Android/iOS apps

This comprehensive Discussion Forum Website provides a complete platform for online communities with powerful features for both users and moderators!

Leave a Reply

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


Macro Nepal Helper