Introduction
The Blog Platform with Like & Share System is a feature-rich content management system that allows users to create, publish, and interact with blog posts. The platform includes comprehensive social features such as liking posts, sharing on social media, commenting system, user profiles, and an admin dashboard. Readers can engage with content through likes, shares, and comments, while authors can manage their posts and track engagement metrics. The system is designed to be responsive, SEO-friendly, and highly interactive.
Project Features
Public Features:
- Browse Blog Posts (Latest, Popular, Featured)
- Search Posts by Title, Content, or Tags
- Filter Posts by Category
- View Single Post with Full Content
- Like/Unlike Posts (with AJAX)
- Share Posts on Social Media (Facebook, Twitter, LinkedIn, WhatsApp)
- Comment on Posts (with nested replies)
- View Author Profiles
- Newsletter Subscription
- Related Posts Suggestions
- Reading Time Estimation
- Post Views Counter
User Features:
- User Registration and Login
- Create, Edit, Delete Own Posts
- Manage Personal Profile
- View Liked Posts
- Track Own Posts Performance
- Comment on Any Post
- Manage Own Comments
- Save Favorite Posts
- Follow Other Authors
- Receive Notifications
Admin Features:
- Admin Dashboard with Analytics
- User Management (CRUD operations)
- Post Management (Approve, Edit, Delete)
- Category Management
- Tag Management
- Comment Moderation
- Site Settings Configuration
- View Reports and Statistics
- Manage Advertisements
- SEO Settings
- Backup and Restore
Social Features:
- Real-time Like System (AJAX)
- Social Media Sharing Buttons
- Share Count Tracking
- Embedded Social Media Posts
- User Activity Feed
- Popular Posts Widget
- Trending Topics
- User Engagement Analytics
Project File Structure
blog-platform/ │ ├── assets/ │ ├── css/ │ │ ├── style.css │ │ ├── admin-style.css │ │ ├── responsive.css │ │ └── font-awesome.min.css │ ├── js/ │ │ ├── main.js │ │ ├── like-system.js │ │ ├── comment-system.js │ │ ├── share-system.js │ │ ├── search.js │ │ └── admin.js │ ├── images/ │ │ ├── posts/ │ │ ├── avatars/ │ │ └── categories/ │ └── plugins/ │ ├── ckeditor/ │ └── summernote/ │ ├── uploads/ │ ├── posts/ │ ├── thumbnails/ │ └── avatars/ │ ├── database/ │ └── blog_platform.sql │ ├── includes/ │ ├── config.php │ ├── db_connection.php │ ├── functions.php │ ├── auth.php │ ├── session.php │ └── seo.php │ ├── admin/ │ ├── index.php │ ├── login.php │ ├── logout.php │ ├── dashboard.php │ ├── posts.php │ ├── add-post.php │ ├── edit-post.php │ ├── delete-post.php │ ├── categories.php │ ├── add-category.php │ ├── edit-category.php │ ├── tags.php │ ├── users.php │ ├── comments.php │ ├── settings.php │ ├── profile.php │ ├── reports.php │ └── backup.php │ ├── user/ │ ├── index.php │ ├── register.php │ ├── login.php │ ├── logout.php │ ├── dashboard.php │ ├── profile.php │ ├── edit-profile.php │ ├── my-posts.php │ ├── add-post.php │ ├── edit-post.php │ ├── liked-posts.php │ ├── saved-posts.php │ └── notifications.php │ ├── api/ │ ├── like.php │ ├── unlike.php │ ├── share.php │ ├── comment.php │ ├── reply.php │ ├── delete-comment.php │ ├── search.php │ ├── subscribe.php │ └── get-posts.php │ ├── includes/ │ ├── post.php │ ├── category.php │ ├── comment.php │ ├── like.php │ └── share.php │ ├── index.php ├── blog.php ├── single.php ├── category.php ├── author.php ├── search.php ├── about.php ├── contact.php ├── privacy-policy.php ├── terms.php ├── sitemap.php ├── 404.php ├── .htaccess └── robots.txt
Database Schema (blog_platform.sql)
```sql
-- Create Database
CREATE DATABASE IF NOT EXISTS blog_platform;
USE blog_platform;
-- Table: users
CREATE TABLE users (
user_id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) UNIQUE NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL,
full_name VARCHAR(100),
bio TEXT,
avatar VARCHAR(255),
role ENUM('admin', 'author', 'subscriber') DEFAULT 'subscriber',
is_active BOOLEAN DEFAULT TRUE,
email_verified BOOLEAN DEFAULT FALSE,
verification_token VARCHAR(100),
reset_token VARCHAR(100),
reset_expires DATETIME,
last_login DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert default admin
INSERT INTO users (username, email, password, full_name, role)
VALUES ('admin', '[email protected]', MD5('Admin@123'), 'System Administrator', 'admin');
-- Table: categories
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL,
category_slug VARCHAR(100) UNIQUE NOT NULL,
category_description TEXT,
category_image VARCHAR(255),
parent_category INT DEFAULT NULL,
meta_title VARCHAR
-- Table: categories (continued)
CREATE TABLE categories (
category_id INT PRIMARY KEY AUTO_INCREMENT,
category_name VARCHAR(100) NOT NULL,
category_slug VARCHAR(100) UNIQUE NOT NULL,
category_description TEXT,
category_image VARCHAR(255),
parent_category INT DEFAULT NULL,
meta_title VARCHAR(100),
meta_description TEXT,
is_active BOOLEAN DEFAULT TRUE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (parent_category) REFERENCES categories(category_id) ON DELETE SET NULL
);
-- Insert default categories
INSERT INTO categories (category_name, category_slug, category_description) VALUES
('Technology', 'technology', 'Latest technology trends, gadgets, and innovations'),
('Lifestyle', 'lifestyle', 'Lifestyle tips, health, wellness, and personal development'),
('Travel', 'travel', 'Travel guides, tips, and destination reviews'),
('Food', 'food', 'Recipes, restaurant reviews, and culinary adventures'),
('Fashion', 'fashion', 'Fashion trends, style tips, and brand reviews');
-- Table: tags
CREATE TABLE tags (
tag_id INT PRIMARY KEY AUTO_INCREMENT,
tag_name VARCHAR(50) UNIQUE NOT NULL,
tag_slug VARCHAR(50) UNIQUE NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table: posts
CREATE TABLE posts (
post_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
category_id INT,
title VARCHAR(255) NOT NULL,
slug VARCHAR(255) UNIQUE NOT NULL,
content LONGTEXT NOT NULL,
excerpt TEXT,
featured_image VARCHAR(255),
status ENUM('draft', 'published', 'archived') DEFAULT 'draft',
is_featured BOOLEAN DEFAULT FALSE,
is_popular BOOLEAN DEFAULT FALSE,
views INT DEFAULT 0,
reading_time INT,
meta_title VARCHAR(100),
meta_description TEXT,
meta_keywords VARCHAR(255),
published_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (category_id) REFERENCES categories(category_id) ON DELETE SET NULL,
INDEX idx_status (status),
INDEX idx_published (published_at),
INDEX idx_featured (is_featured),
FULLTEXT INDEX idx_search (title, content, excerpt)
);
-- Table: post_tags
CREATE TABLE post_tags (
post_id INT,
tag_id INT,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (tag_id) REFERENCES tags(tag_id) ON DELETE CASCADE
);
-- Table: likes
CREATE TABLE likes (
like_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
post_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_like (user_id, post_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
INDEX idx_post_likes (post_id)
);
-- Table: shares
CREATE TABLE shares (
share_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
post_id INT NOT NULL,
platform ENUM('facebook', 'twitter', 'linkedin', 'whatsapp', 'email', 'copy') NOT NULL,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
INDEX idx_post_shares (post_id)
);
-- Table: comments
CREATE TABLE comments (
comment_id INT PRIMARY KEY AUTO_INCREMENT,
post_id INT NOT NULL,
user_id INT,
parent_comment_id INT DEFAULT NULL,
author_name VARCHAR(100),
author_email VARCHAR(100),
comment TEXT NOT NULL,
status ENUM('pending', 'approved', 'spam', 'trash') DEFAULT 'pending',
ip_address VARCHAR(45),
user_agent TEXT,
like_count INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
FOREIGN KEY (parent_comment_id) REFERENCES comments(comment_id) ON DELETE CASCADE,
INDEX idx_post_comments (post_id),
INDEX idx_status (status)
);
-- Table: comment_likes
CREATE TABLE comment_likes (
comment_like_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
comment_id INT NOT NULL,
ip_address VARCHAR(45),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_comment_like (user_id, comment_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (comment_id) REFERENCES comments(comment_id) ON DELETE CASCADE
);
-- Table: saved_posts
CREATE TABLE saved_posts (
saved_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
post_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_save (user_id, post_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (post_id) REFERENCES posts(post_id) ON DELETE CASCADE
);
-- Table: followers
CREATE TABLE followers (
follower_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
follower_user_id INT NOT NULL,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
UNIQUE KEY unique_follow (user_id, follower_user_id),
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
FOREIGN KEY (follower_user_id) REFERENCES users(user_id) ON DELETE CASCADE
);
-- Table: notifications
CREATE TABLE notifications (
notification_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
type ENUM('like', 'comment', 'reply', 'share', 'follow', 'mention') NOT NULL,
reference_id INT,
reference_type VARCHAR(50),
message TEXT,
is_read BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE,
INDEX idx_user_notifications (user_id, is_read)
);
-- Table: subscribers
CREATE TABLE subscribers (
subscriber_id INT PRIMARY KEY AUTO_INCREMENT,
email VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(100),
is_active BOOLEAN DEFAULT TRUE,
verified_at DATETIME,
verification_token VARCHAR(100),
unsubscribed_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table: newsletter_queue
CREATE TABLE newsletter_queue (
queue_id INT PRIMARY KEY AUTO_INCREMENT,
subject VARCHAR(255) NOT NULL,
content TEXT NOT NULL,
status ENUM('pending', 'sending', 'sent', 'failed') DEFAULT 'pending',
sent_count INT DEFAULT 0,
total_count INT,
scheduled_at DATETIME,
sent_at DATETIME,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table: site_settings
CREATE TABLE site_settings (
setting_id INT PRIMARY KEY AUTO_INCREMENT,
setting_key VARCHAR(100) UNIQUE NOT NULL,
setting_value TEXT,
setting_type ENUM('text', 'textarea', 'image', 'boolean', 'number') DEFAULT 'text',
setting_description TEXT,
updated_by INT,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
-- Insert default settings
INSERT INTO site_settings (setting_key, setting_value, setting_type, setting_description) VALUES
('site_name', 'BlogPlatform', 'text', 'Website name'),
('site_description', 'A modern blog platform with like and share system', 'textarea', 'Website description'),
('site_keywords', 'blog, articles, posts, sharing', 'text', 'Default meta keywords'),
('site_logo', 'logo.png', 'image', 'Website logo'),
('site_favicon', 'favicon.ico', 'image', 'Website favicon'),
('posts_per_page', '10', 'number', 'Number of posts per page'),
('allow_registration', '1', 'boolean', 'Allow new user registration'),
('comment_moderation', '1', 'boolean', 'Moderate comments before publishing'),
('facebook_url', '', 'text', 'Facebook page URL'),
('twitter_url', '', 'text', 'Twitter profile URL'),
('instagram_url', '', 'text', 'Instagram profile URL'),
('linkedin_url', '', 'text', 'LinkedIn profile URL'),
('mailchimp_api_key', '', 'text', 'MailChimp API key'),
('mailchimp_list_id', '', 'text', 'MailChimp list ID'),
('google_analytics_id', '', 'text', 'Google Analytics tracking ID'),
('disqus_shortname', '', 'text', 'Disqus shortname for comments'),
('maintenance_mode', '0', 'boolean', 'Enable maintenance mode');
-- Table: ad_spots
CREATE TABLE ad_spots (
ad_id INT PRIMARY KEY AUTO_INCREMENT,
ad_name VARCHAR(100) NOT NULL,
ad_code TEXT,
ad_image VARCHAR(255),
ad_url VARCHAR(255),
position ENUM('header', 'sidebar', 'content_top', 'content_bottom', 'footer') NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
start_date DATETIME,
end_date DATETIME,
impressions INT DEFAULT 0,
clicks INT DEFAULT 0,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
-- Table: login_attempts
CREATE TABLE login_attempts (
attempt_id INT PRIMARY KEY AUTO_INCREMENT,
ip_address VARCHAR(45) NOT NULL,
username VARCHAR(100),
attempt_time TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_ip (ip_address)
);
-- Table: activity_logs
CREATE TABLE activity_logs (
log_id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
action VARCHAR(100) NOT NULL,
description TEXT,
ip_address VARCHAR(45),
user_agent TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE SET NULL,
INDEX idx_user_activity (user_id)
);
Core PHP Files
1. includes/config.php
<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_platform');
// Application configuration
define('SITE_NAME', 'BlogPlatform');
define('SITE_URL', 'http://localhost/blog-platform/');
define('ADMIN_URL', SITE_URL . 'admin/');
define('USER_URL', SITE_URL . 'user/');
define('API_URL', SITE_URL . 'api/');
define('UPLOAD_DIR', $_SERVER['DOCUMENT_ROOT'] . '/blog-platform/uploads/');
define('POST_IMAGE_DIR', UPLOAD_DIR . 'posts/');
define('AVATAR_DIR', UPLOAD_DIR . 'avatars/');
define('CATEGORY_IMAGE_DIR', UPLOAD_DIR . 'categories/');
// Site settings
define('POSTS_PER_PAGE', 10);
define('COMMENTS_PER_PAGE', 20);
define('ALLOW_REGISTRATION', true);
define('COMMENT_MODERATION', true);
define('ENABLE_LIKES', true);
define('ENABLE_SHARES', true);
define('ENABLE_COMMENTS', true);
// Security settings
define('SESSION_TIMEOUT', 3600); // 1 hour
define('MAX_LOGIN_ATTEMPTS', 5);
define('LOCKOUT_TIME', 900); // 15 minutes
define('CSRF_PROTECTION', true);
define('HASH_COST', 10);
// File upload settings
define('MAX_FILE_SIZE', 5242880); // 5MB
define('ALLOWED_IMAGE_TYPES', 'jpg,jpeg,png,gif,webp');
define('IMAGE_QUALITY', 80);
// Cache settings
define('ENABLE_CACHE', true);
define('CACHE_DIR', $_SERVER['DOCUMENT_ROOT'] . '/blog-platform/cache/');
define('CACHE_TIME', 3600); // 1 hour
// Social media settings
define('FACEBOOK_APP_ID', '');
define('FACEBOOK_APP_SECRET', '');
define('TWITTER_API_KEY', '');
define('TWITTER_API_SECRET', '');
define('GOOGLE_CLIENT_ID', '');
define('GOOGLE_CLIENT_SECRET', '');
// Email settings
define('SMTP_HOST', 'smtp.gmail.com');
define('SMTP_PORT', 587);
define('SMTP_USER', '[email protected]');
define('SMTP_PASS', 'your-password');
define('SMTP_ENCRYPTION', 'tls');
define('FROM_EMAIL', '[email protected]');
define('FROM_NAME', 'BlogPlatform');
// Start session if not started
if (session_status() == PHP_SESSION_NONE) {
session_start();
}
// Set timezone
date_default_timezone_set('UTC');
// Error reporting (disable in production)
error_reporting(E_ALL);
ini_set('display_errors', 1);
?>
2. includes/db_connection.php
<?php
require_once 'config.php';
class Database {
private $connection;
private static $instance = null;
private $queries = [];
private $query_count = 0;
private function __construct() {
$this->connect();
}
public static function getInstance() {
if (self::$instance === null) {
self::$instance = new Database();
}
return self::$instance;
}
private function connect() {
$this->connection = new mysqli(DB_HOST, DB_USER, DB_PASS, DB_NAME);
if ($this->connection->connect_error) {
error_log("Database connection failed: " . $this->connection->connect_error);
die("Database connection failed. Please try again later.");
}
$this->connection->set_charset("utf8mb4");
}
public function getConnection() {
return $this->connection;
}
public function escapeString($string) {
return $this->connection->real_escape_string(trim($string));
}
public function prepare($sql) {
return $this->connection->prepare($sql);
}
public function query($sql) {
$this->queries[] = $sql;
$this->query_count++;
return $this->connection->query($sql);
}
public function getLastInsertId() {
return $this->connection->insert_id;
}
public function affectedRows() {
return $this->connection->affected_rows;
}
public function beginTransaction() {
$this->connection->begin_transaction();
}
public function commit() {
$this->connection->commit();
}
public function rollback() {
$this->connection->rollback();
}
public function getQueries() {
return $this->queries;
}
public function getQueryCount() {
return $this->query_count;
}
public function __destruct() {
if ($this->connection) {
$this->connection->close();
}
}
}
// Global database instance
$db = Database::getInstance();
$conn = $db->getConnection();
?>
3. includes/functions.php
<?php
require_once 'db_connection.php';
// Redirect to specified page
function redirect($url) {
header("Location: $url");
exit();
}
// Check if user is logged in
function isLoggedIn() {
return isset($_SESSION['user_id']) && !empty($_SESSION['user_id']);
}
// Check if user is admin
function isAdmin() {
return isset($_SESSION['user_role']) && $_SESSION['user_role'] === 'admin';
}
// Check if user is author
function isAuthor() {
return isset($_SESSION['user_role']) && ($_SESSION['user_role'] === 'author' || $_SESSION['user_role'] === 'admin');
}
// Get current user ID
function getCurrentUserId() {
return $_SESSION['user_id'] ?? 0;
}
// Get current user role
function getCurrentUserRole() {
return $_SESSION['user_role'] ?? 'guest';
}
// Generate slug from string
function createSlug($string) {
$string = strtolower($string);
$string = preg_replace('/[^a-z0-9-]/', '-', $string);
$string = preg_replace('/-+/', '-', $string);
return trim($string, '-');
}
// Get unique slug
function getUniqueSlug($table, $field, $value, $id = null) {
global $conn;
$slug = createSlug($value);
$original_slug = $slug;
$counter = 1;
while (true) {
$sql = "SELECT COUNT(*) as count FROM $table WHERE $field = '$slug'";
if ($id) {
$sql .= " AND post_id != $id";
}
$result = $conn->query($sql);
$row = $result->fetch_assoc();
if ($row['count'] == 0) {
break;
}
$slug = $original_slug . '-' . $counter;
$counter++;
}
return $slug;
}
// Format date
function formatDate($date, $format = 'M d, Y') {
return date($format, strtotime($date));
}
// Time ago function
function timeAgo($datetime) {
$time = strtotime($datetime);
$now = time();
$diff = $now - $time;
if ($diff < 60) {
return $diff . ' seconds ago';
} elseif ($diff < 3600) {
$mins = floor($diff / 60);
return $mins . ' minute' . ($mins > 1 ? 's' : '') . ' ago';
} elseif ($diff < 86400) {
$hours = floor($diff / 3600);
return $hours . ' hour' . ($hours > 1 ? 's' : '') . ' ago';
} elseif ($diff < 604800) {
$days = floor($diff / 86400);
return $days . ' day' . ($days > 1 ? 's' : '') . ' ago';
} elseif ($diff < 2592000) {
$weeks = floor($diff / 604800);
return $weeks . ' week' . ($weeks > 1 ? 's' : '') . ' ago';
} elseif ($diff < 31536000) {
$months = floor($diff / 2592000);
return $months . ' month' . ($months > 1 ? 's' : '') . ' ago';
} else {
$years = floor($diff / 31536000);
return $years . ' year' . ($years > 1 ? 's' : '') . ' ago';
}
}
// Get reading time
function getReadingTime($content, $words_per_minute = 200) {
$word_count = str_word_count(strip_tags($content));
$minutes = ceil($word_count / $words_per_minute);
return $minutes < 1 ? 1 : $minutes;
}
// Truncate text
function truncateText($text, $length = 100, $suffix = '...') {
if (strlen($text) <= $length) {
return $text;
}
$text = substr($text, 0, $length);
$text = substr($text, 0, strrpos($text, ' '));
return $text . $suffix;
}
// Get excerpt from content
function getExcerpt($content, $length = 200) {
$content = strip_tags($content);
return truncateText($content, $length);
}
// Sanitize input
function sanitize($input) {
global $conn;
if (is_array($input)) {
foreach ($input as $key => $value) {
$input[$key] = sanitize($value);
}
return $input;
}
$input = trim($input);
$input = stripslashes($input);
$input = htmlspecialchars($input, ENT_QUOTES, 'UTF-8');
$input = $conn->real_escape_string($input);
return $input;
}
// Generate CSRF token
function generateCSRFToken() {
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
return $_SESSION['csrf_token'];
}
// Verify CSRF token
function verifyCSRFToken($token) {
if (!CSRF_PROTECTION) {
return true;
}
if (!isset($_SESSION['csrf_token']) || empty($token)) {
return false;
}
return hash_equals($_SESSION['csrf_token'], $token);
}
// Upload image
function uploadImage($file, $target_dir, $filename = null) {
// Check for errors
if ($file['error'] !== UPLOAD_ERR_OK) {
return ['success' => false, 'message' => 'Upload failed with error code ' . $file['error']];
}
// Check file size
if ($file['size'] > MAX_FILE_SIZE) {
return ['success' => false, 'message' => 'File is too large. Maximum size is ' . (MAX_FILE_SIZE / 1048576) . 'MB'];
}
// Get file info
$image_info = getimagesize($file['tmp_name']);
if ($image_info === false) {
return ['success' => false, 'message' => 'File is not a valid image'];
}
// Check file type
$extension = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$allowed_types = explode(',', ALLOWED_IMAGE_TYPES);
if (!in_array($extension, $allowed_types)) {
return ['success' => false, 'message' => 'File type not allowed. Allowed types: ' . ALLOWED_IMAGE_TYPES];
}
// Generate filename if not provided
if (!$filename) {
$filename = uniqid() . '_' . time() . '.' . $extension;
}
$target_path = $target_dir . $filename;
// Create directory if not exists
if (!file_exists($target_dir)) {
mkdir($target_dir, 0755, true);
}
// Upload file
if (move_uploaded_file($file['tmp_name'], $target_path)) {
// Optimize image
optimizeImage($target_path, $extension);
return ['success' => true, 'filename' => $filename];
} else {
return ['success' => false, 'message' => 'Failed to move uploaded file'];
}
}
// Optimize image
function optimizeImage($path, $extension) {
switch ($extension) {
case 'jpg':
case 'jpeg':
$image = imagecreatefromjpeg($path);
imagejpeg($image, $path, IMAGE_QUALITY);
break;
case 'png':
$image = imagecreatefrompng($path);
imagepng($image, $path, 9);
break;
case 'gif':
$image = imagecreatefromgif($path);
imagegif($image, $path);
break;
case 'webp':
$image = imagecreatefromwebp($path);
imagewebp($image, $path, IMAGE_QUALITY);
break;
}
if (isset($image)) {
imagedestroy($image);
}
}
// Get post likes count
function getPostLikes($post_id) {
global $conn;
$sql = "SELECT COUNT(*) as count FROM likes WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'];
}
// Check if user liked post
function userLikedPost($post_id, $user_id = null) {
global $conn;
if (!$user_id) {
$user_id = getCurrentUserId();
}
if (!$user_id) {
return false;
}
$sql = "SELECT COUNT(*) as count FROM likes WHERE post_id = ? AND user_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $post_id, $user_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'] > 0;
}
// Get post shares count
function getPostShares($post_id) {
global $conn;
$sql = "SELECT COUNT(*) as count FROM shares WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'];
}
// Get post comments count
function getPostComments($post_id) {
global $conn;
$sql = "SELECT COUNT(*) as count FROM comments WHERE post_id = ? AND status = 'approved'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'];
}
// Get post views
function getPostViews($post_id) {
global $conn;
$sql = "SELECT views FROM posts WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['views'] ?? 0;
}
// Increment post views
function incrementPostViews($post_id) {
global $conn;
$sql = "UPDATE posts SET views = views + 1 WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
return $stmt->execute();
}
// Get related posts
function getRelatedPosts($post_id, $category_id, $limit = 3) {
global $conn;
$sql = "SELECT p.*, u.username, u.full_name, u.avatar,
(SELECT COUNT(*) FROM likes WHERE post_id = p.post_id) as like_count,
(SELECT COUNT(*) FROM comments WHERE post_id = p.post_id AND status = 'approved') as comment_count
FROM posts p
LEFT JOIN users u ON p.user_id = u.user_id
WHERE p.post_id != ? AND p.category_id = ? AND p.status = 'published'
ORDER BY p.published_at DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iii", $post_id, $category_id, $limit);
$stmt->execute();
$result = $stmt->get_result();
$posts = [];
while ($row = $result->fetch_assoc()) {
$posts[] = $row;
}
return $posts;
}
// Get popular posts
function getPopularPosts($limit = 5) {
global $conn;
$sql = "SELECT p.*, u.username, u.full_name, u.avatar,
(SELECT COUNT(*) FROM likes WHERE post_id = p.post_id) as like_count,
(SELECT COUNT(*) FROM comments WHERE post_id = p.post_id AND status = 'approved') as comment_count
FROM posts p
LEFT JOIN users u ON p.user_id = u.user_id
WHERE p.status = 'published'
ORDER BY p.views DESC, p.likes DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $limit);
$stmt->execute();
$result = $stmt->get_result();
$posts = [];
while ($row = $result->fetch_assoc()) {
$posts[] = $row;
}
return $posts;
}
// Get featured posts
function getFeaturedPosts($limit = 3) {
global $conn;
$sql = "SELECT p.*, u.username, u.full_name, u.avatar,
(SELECT COUNT(*) FROM likes WHERE post_id = p.post_id) as like_count,
(SELECT COUNT(*) FROM comments WHERE post_id = p.post_id AND status = 'approved') as comment_count
FROM posts p
LEFT JOIN users u ON p.user_id = u.user_id
WHERE p.status = 'published' AND p.is_featured = 1
ORDER BY p.published_at DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $limit);
$stmt->execute();
$result = $stmt->get_result();
$posts = [];
while ($row = $result->fetch_assoc()) {
$posts[] = $row;
}
return $posts;
}
// Get categories with post count
function getCategoriesWithCount() {
global $conn;
$sql = "SELECT c.*, COUNT(p.post_id) as post_count
FROM categories c
LEFT JOIN posts p ON c.category_id = p.category_id AND p.status = 'published'
WHERE c.is_active = 1
GROUP BY c.category_id
ORDER BY c.category_name";
$result = $conn->query($sql);
$categories = [];
while ($row = $result->fetch_assoc()) {
$categories[] = $row;
}
return $categories;
}
// Get tags with post count
function getTagsWithCount($limit = 20) {
global $conn;
$sql = "SELECT t.*, COUNT(pt.post_id) as post_count
FROM tags t
LEFT JOIN post_tags pt ON t.tag_id = pt.tag_id
LEFT JOIN posts p ON pt.post_id = p.post_id AND p.status = 'published'
GROUP BY t.tag_id
ORDER BY post_count DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $limit);
$stmt->execute();
$result = $stmt->get_result();
$tags = [];
while ($row = $result->fetch_assoc()) {
$tags[] = $row;
}
return $tags;
}
// Create notification
function createNotification($user_id, $type, $reference_id, $reference_type, $message) {
global $conn;
$sql = "INSERT INTO notifications (user_id, type, reference_id, reference_type, message)
VALUES (?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("isiss", $user_id, $type, $reference_id, $reference_type, $message);
return $stmt->execute();
}
// Get user notifications
function getUserNotifications($user_id, $limit = 10) {
global $conn;
$sql = "SELECT * FROM notifications
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $user_id, $limit);
$stmt->execute();
$result = $stmt->get_result();
$notifications = [];
while ($row = $result->fetch_assoc()) {
$notifications[] = $row;
}
return $notifications;
}
// Mark notification as read
function markNotificationAsRead($notification_id) {
global $conn;
$sql = "UPDATE notifications SET is_read = 1 WHERE notification_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $notification_id);
return $stmt->execute();
}
// Mark all notifications as read
function markAllNotificationsAsRead($user_id) {
global $conn;
$sql = "UPDATE notifications SET is_read = 1 WHERE user_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $user_id);
return $stmt->execute();
}
// Get unread notification count
function getUnreadNotificationCount($user_id) {
global $conn;
$sql = "SELECT COUNT(*) as count FROM notifications WHERE user_id = ? AND is_read = 0";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $user_id);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'];
}
// Log activity
function logActivity($user_id, $action, $description) {
global $conn;
$ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$sql = "INSERT INTO activity_logs (user_id, action, description, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("issss", $user_id, $action, $description, $ip_address, $user_agent);
return $stmt->execute();
}
// Check login attempts
function checkLoginAttempts($ip_address) {
global $conn;
$lockout_time = date('Y-m-d H:i:s', time() - LOCKOUT_TIME);
$sql = "SELECT COUNT(*) as count FROM login_attempts
WHERE ip_address = ? AND attempt_time > ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $ip_address, $lockout_time);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();
return $row['count'] >= MAX_LOGIN_ATTEMPTS;
}
// Record login attempt
function recordLoginAttempt($ip_address, $username = null) {
global $conn;
$sql = "INSERT INTO login_attempts (ip_address, username) VALUES (?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ss", $ip_address, $username);
return $stmt->execute();
}
// Clear login attempts
function clearLoginAttempts($ip_address) {
global $conn;
$sql = "DELETE FROM login_attempts WHERE ip_address = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $ip_address);
return $stmt->execute();
}
// Send email
function sendEmail($to, $subject, $message, $from = null, $from_name = null) {
require_once 'Mail.php';
$headers = [
'MIME-Version' => '1.0',
'Content-type' => 'text/html; charset=UTF-8',
'From' => ($from_name ? $from_name . ' ' : '') . '<' . ($from ?: FROM_EMAIL) . '>',
'Reply-To' => FROM_EMAIL,
'X-Mailer' => 'PHP/' . phpversion()
];
$smtp = Mail::factory('smtp', [
'host' => SMTP_HOST,
'port' => SMTP_PORT,
'auth' => true,
'username' => SMTP_USER,
'password' => SMTP_PASS,
'debug' => false
]);
$mail = $smtp->send($to, $headers, $message);
if (PEAR::isError($mail)) {
error_log('Email sending failed: ' . $mail->getMessage());
return false;
}
return true;
}
// Generate random password
function generateRandomPassword($length = 10) {
$chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_-=+';
$password = '';
for ($i = 0; $i < $length; $i++) {
$password .= $chars[random_int(0, strlen($chars) - 1)];
}
return $password;
}
// Get site setting
function getSetting($key, $default = null) {
global $conn;
static $settings = null;
if ($settings === null) {
$result = $conn->query("SELECT setting_key, setting_value FROM site_settings");
while ($row = $result->fetch_assoc()) {
$settings[$row['setting_key']] = $row['setting_value'];
}
}
return $settings[$key] ?? $default;
}
// Update site setting
function updateSetting($key, $value) {
global $conn;
$sql = "UPDATE site_settings SET setting_value = ?, updated_by = ?, updated_at = NOW() WHERE setting_key = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("sis", $value, $_SESSION['user_id'], $key);
return $stmt->execute();
}
// Get social share URLs
function getShareUrls($post) {
$url = urlencode(SITE_URL . 'post/' . $post['slug']);
$title = urlencode($post['title']);
$excerpt = urlencode(truncateText(strip_tags($post['excerpt'] ?: $post['content']), 100));
return [
'facebook' => 'https://www.facebook.com/sharer/sharer.php?u=' . $url,
'twitter' => 'https://twitter.com/intent/tweet?url=' . $url . '&text=' . $title,
'linkedin' => 'https://www.linkedin.com/shareArticle?mini=true&url=' . $url . '&title=' . $title . '&summary=' . $excerpt,
'whatsapp' => 'https://wa.me/?text=' . $title . '%20' . $url,
'email' => 'mailto:?subject=' . $title . '&body=' . $excerpt . '%0A%0A' . urldecode($url)
];
}
// Get JSON response
function jsonResponse($data, $status_code = 200) {
http_response_code($status_code);
header('Content-Type: application/json');
echo json_encode($data);
exit();
}
// Pagination
function getPagination($current_page, $total_pages, $url_pattern) {
$html = '<ul class="pagination">';
// Previous button
if ($current_page > 1) {
$html .= '<li><a href="' . str_replace('{page}', $current_page - 1, $url_pattern) . '">« Previous</a></li>';
}
// Page numbers
$start = max(1, $current_page - 2);
$end = min($total_pages, $current_page + 2);
if ($start > 1) {
$html .= '<li><a href="' . str_replace('{page}', 1, $url_pattern) . '">1</a></li>';
if ($start > 2) {
$html .= '<li><span>...</span></li>';
}
}
for ($i = $start; $i <= $end; $i++) {
if ($i == $current_page) {
$html .= '<li class="active"><span>' . $i . '</span></li>';
} else {
$html .= '<li><a href="' . str_replace('{page}', $i, $url_pattern) . '">' . $i . '</a></li>';
}
}
if ($end < $total_pages) {
if ($end < $total_pages - 1) {
$html .= '<li><span>...</span></li>';
}
$html .= '<li><a href="' . str_replace('{page}', $total_pages, $url_pattern) . '">' . $total_pages . '</a></li>';
}
// Next button
if ($current_page < $total_pages) {
$html .= '<li><a href="' . str_replace('{page}', $current_page + 1, $url_pattern) . '">Next »</a></li>';
}
$html .= '</ul>';
return $html;
}
?>
4. index.php (Homepage)
<?php
require_once 'includes/config.php';
require_once 'includes/functions.php';
// Get featured posts
$featured_posts = getFeaturedPosts(3);
// Get latest posts
$page = isset($_GET['page']) ? (int)$_GET['page'] : 1;
$offset = ($page - 1) * POSTS_PER_PAGE;
$sql = "SELECT p.*, u.username, u.full_name, u.avatar,
c.category_name, c.category_slug,
(SELECT COUNT(*) FROM likes WHERE post_id = p.post_id) as like_count,
(SELECT COUNT(*) FROM comments WHERE post_id = p.post_id AND status = 'approved') as comment_count
FROM posts p
LEFT JOIN users u ON p.user_id = u.user_id
LEFT JOIN categories c ON p.category_id = c.category_id
WHERE p.status = 'published'
ORDER BY p.published_at DESC
LIMIT ? OFFSET ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", POSTS_PER_PAGE, $offset);
$stmt->execute();
$recent_posts = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get total posts count for pagination
$total_result = $conn->query("SELECT COUNT(*) as count FROM posts WHERE status = 'published'");
$total_posts = $total_result->fetch_assoc()['count'];
$total_pages = ceil($total_posts / POSTS_PER_PAGE);
// Get popular posts
$popular_posts = getPopularPosts(5);
// Get categories
$categories = getCategoriesWithCount();
// Get tags
$tags = getTagsWithCount(20);
// Get site settings
$site_name = getSetting('site_name', 'BlogPlatform');
$site_description = getSetting('site_description', 'A modern blog platform');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $site_name; ?> - <?php echo $site_description; ?></title>
<meta name="description" content="<?php echo $site_description; ?>">
<meta name="keywords" content="<?php echo getSetting('site_keywords', 'blog, articles'); ?>">
<!-- Open Graph Tags -->
<meta property="og:title" content="<?php echo $site_name; ?>">
<meta property="og:description" content="<?php echo $site_description; ?>">
<meta property="og:type" content="website">
<meta property="og:url" content="<?php echo SITE_URL; ?>">
<meta property="og:image" content="<?php echo SITE_URL . 'assets/images/og-image.jpg'; ?>">
<!-- Twitter Card Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo $site_name; ?>">
<meta name="twitter:description" content="<?php echo $site_description; ?>">
<meta name="twitter:image" content="<?php echo SITE_URL . 'assets/images/twitter-image.jpg'; ?>">
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- Navigation -->
<nav class="navbar">
<div class="container">
<div class="nav-brand">
<a href="index.php">
<img src="assets/images/<?php echo getSetting('site_logo', 'logo.png'); ?>" alt="<?php echo $site_name; ?>">
</a>
</div>
<div class="nav-search">
<form action="search.php" method="GET">
<input type="text" name="q" placeholder="Search articles...">
<button type="submit"><i class="fas fa-search"></i></button>
</form>
</div>
<ul class="nav-menu">
<li><a href="index.php" class="active">Home</a></li>
<li><a href="blog.php">Blog</a></li>
<li><a href="about.php">About</a></li>
<li><a href="contact.php">Contact</a></li>
<?php if (isLoggedIn()): ?>
<li class="dropdown">
<a href="#" class="dropdown-toggle">
<img src="uploads/avatars/<?php echo $_SESSION['user_avatar'] ?? 'default-avatar.png'; ?>" alt="Avatar">
<?php echo $_SESSION['username']; ?>
</a>
<ul class="dropdown-menu">
<li><a href="user/dashboard.php"><i class="fas fa-tachometer-alt"></i> Dashboard</a></li>
<li><a href="user/profile.php"><i class="fas fa-user"></i> Profile</a></li>
<li><a href="user/my-posts.php"><i class="fas fa-pen"></i> My Posts</a></li>
<li><a href="user/liked-posts.php"><i class="fas fa-heart"></i> Liked Posts</a></li>
<li><a href="user/saved-posts.php"><i class="fas fa-bookmark"></i> Saved Posts</a></li>
<li><a href="user/notifications.php"><i class="fas fa-bell"></i> Notifications</a></li>
<li><a href="logout.php"><i class="fas fa-sign-out-alt"></i> Logout</a></li>
</ul>
</li>
<?php else: ?>
<li><a href="user/login.php">Login</a></li>
<li><a href="user/register.php" class="btn-register">Register</a></li>
<?php endif; ?>
</ul>
</div>
</nav>
<!-- Hero Section with Featured Posts -->
<?php if (!empty($featured_posts)): ?>
<section class="hero">
<div class="container">
<h1>Featured Stories</h1>
<div class="featured-grid">
<?php foreach ($featured_posts as $index => $post): ?>
<div class="featured-card <?php echo $index === 0 ? 'featured-main' : ''; ?>">
<div class="featured-image">
<img src="uploads/posts/<?php echo $post['featured_image'] ?: 'default-post.jpg'; ?>"
alt="<?php echo $post['title']; ?>">
</div>
<div class="featured-content">
<div class="post-meta">
<span class="post-category">
<a href="category.php?slug=<?php echo $post['category_slug']; ?>">
<?php echo $post['category_name']; ?>
</a>
</span>
<span class="post-date">
<i class="far fa-clock"></i> <?php echo timeAgo($post['published_at']); ?>
</span>
</div>
<h3><a href="post.php?slug=<?php echo $post['slug']; ?>"><?php echo $post['title']; ?></a></h3>
<p class="post-excerpt"><?php echo truncateText(strip_tags($post['excerpt'] ?: $post['content']), 150); ?></p>
<div class="post-footer">
<div class="post-author">
<img src="uploads/avatars/<?php echo $post['avatar'] ?: 'default-avatar.png'; ?>" alt="<?php echo $post['username']; ?>">
<span><?php echo $post['full_name'] ?: $post['username']; ?></span>
</div>
<div class="post-stats">
<span><i class="far fa-heart"></i> <?php echo $post['like_count']; ?></span>
<span><i class="far fa-comment"></i> <?php echo $post['comment_count']; ?></span>
<span><i class="far fa-eye"></i> <?php echo $post['views']; ?></span>
</div>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
</div>
</section>
<?php endif; ?>
<!-- Main Content -->
<div class="main-content">
<div class="container">
<div class="content-wrapper">
<!-- Blog Posts -->
<main class="content-main">
<section class="recent-posts">
<h2>Recent Articles</h2>
<?php if (empty($recent_posts)): ?>
<p class="no-posts">No posts found.</p>
<?php else: ?>
<div class="posts-grid">
<?php foreach ($recent_posts as $post): ?>
<article class="post-card">
<div class="post-image">
<a href="post.php?slug=<?php echo $post['slug']; ?>">
<img src="uploads/posts/<?php echo $post['featured_image'] ?: 'default-post.jpg'; ?>"
alt="<?php echo $post['title']; ?>">
</a>
</div>
<div class="post-content">
<div class="post-meta">
<span class="post-category">
<a href="category.php?slug=<?php echo $post['category_slug']; ?>">
<?php echo $post['category_name']; ?>
</a>
</span>
<span class="post-date">
<i class="far fa-calendar"></i> <?php echo formatDate($post['published_at']); ?>
</span>
</div>
<h3><a href="post.php?slug=<?php echo $post['slug']; ?>"><?php echo $post['title']; ?></a></h3>
<p class="post-excerpt"><?php echo truncateText(strip_tags($post['excerpt'] ?: $post['content']), 120); ?></p>
<div class="post-footer">
<div class="post-author">
<img src="uploads/avatars/<?php echo $post['avatar'] ?: 'default-avatar.png'; ?>"
alt="<?php echo $post['username']; ?>">
<span><?php echo $post['full_name'] ?: $post['username']; ?></span>
</div>
<div class="post-stats">
<span class="likes-count" data-post-id="<?php echo $post['post_id']; ?>">
<i class="far fa-heart"></i> <?php echo $post['like_count']; ?>
</span>
<span>
<i class="far fa-comment"></i> <?php echo $post['comment_count']; ?>
</span>
<span>
<i class="far fa-eye"></i> <?php echo $post['views']; ?>
</span>
</div>
</div>
</div>
</article>
<?php endforeach; ?>
</div>
<!-- Pagination -->
<?php if ($total_pages > 1): ?>
<div class="pagination-wrapper">
<?php
$url_pattern = SITE_URL . '?page={page}';
echo getPagination($page, $total_pages, $url_pattern);
?>
</div>
<?php endif; ?>
<?php endif; ?>
</section>
</main>
<!-- Sidebar -->
<aside class="content-sidebar">
<!-- Popular Posts Widget -->
<div class="sidebar-widget">
<h3>Popular Posts</h3>
<?php if (!empty($popular_posts)): ?>
<div class="popular-posts">
<?php foreach ($popular_posts as $post): ?>
<div class="popular-post">
<div class="popular-post-image">
<a href="post.php?slug=<?php echo $post['slug']; ?>">
<img src="uploads/posts/<?php echo $post['featured_image'] ?: 'default-post-thumb.jpg'; ?>"
alt="<?php echo $post['title']; ?>">
</a>
</div>
<div class="popular-post-content">
<h4><a href="post.php?slug=<?php echo $post['slug']; ?>"><?php echo truncateText($post['title'], 50); ?></a></h4>
<div class="post-meta">
<span><i class="far fa-eye"></i> <?php echo $post['views']; ?></span>
<span><i class="far fa-heart"></i> <?php echo $post['like_count']; ?></span>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php else: ?>
<p>No popular posts yet.</p>
<?php endif; ?>
</div>
<!-- Categories Widget -->
<div class="sidebar-widget">
<h3>Categories</h3>
<?php if (!empty($categories)): ?>
<ul class="category-list">
<?php foreach ($categories as $category): ?>
<li>
<a href="category.php?slug=<?php echo $category['category_slug']; ?>">
<?php echo $category['category_name']; ?>
<span class="category-count">(<?php echo $category['post_count']; ?>)</span>
</a>
</li>
<?php endforeach; ?>
</ul>
<?php else: ?>
<p>No categories found.</p>
<?php endif; ?>
</div>
<!-- Tags Widget -->
<div class="sidebar-widget">
<h3>Popular Tags</h3>
<?php if (!empty($tags)): ?>
<div class="tag-cloud">
<?php foreach ($tags as $tag): ?>
<a href="search.php?tag=<?php echo $tag['tag_slug']; ?>" class="tag">
<?php echo $tag['tag_name']; ?>
<span class="tag-count">(<?php echo $tag['post_count']; ?>)</span>
</a>
<?php endforeach; ?>
</div>
<?php else: ?>
<p>No tags found.</p>
<?php endif; ?>
</div>
<!-- Newsletter Widget -->
<div class="sidebar-widget newsletter-widget">
<h3>Subscribe to Newsletter</h3>
<p>Get the latest posts delivered straight to your inbox.</p>
<form id="newsletter-form" class="newsletter-form">
<input type="email" name="email" placeholder="Your email address" required>
<button type="submit" class="btn btn-primary">Subscribe</button>
</form>
<div class="newsletter-message"></div>
</div>
<!-- Social Links -->
<div class="sidebar-widget">
<h3>Follow Us</h3>
<div class="social-links">
<?php if ($facebook_url = getSetting('facebook_url')): ?>
<a href="<?php echo $facebook_url; ?>" target="_blank" class="social-facebook">
<i class="fab fa-facebook-f"></i>
</a>
<?php endif; ?>
<?php if ($twitter_url = getSetting('twitter_url')): ?>
<a href="<?php echo $twitter_url; ?>" target="_blank" class="social-twitter">
<i class="fab fa-twitter"></i>
</a>
<?php endif; ?>
<?php if ($instagram_url = getSetting('instagram_url')): ?>
<a href="<?php echo $instagram_url; ?>" target="_blank" class="social-instagram">
<i class="fab fa-instagram"></i>
</a>
<?php endif; ?>
<?php if ($linkedin_url = getSetting('linkedin_url')): ?>
<a href="<?php echo $linkedin_url; ?>" target="_blank" class="social-linkedin">
<i class="fab fa-linkedin-in"></i>
</a>
<?php endif; ?>
</div>
</div>
</aside>
</div>
</div>
</div>
<!-- Footer -->
<footer class="footer">
<div class="container">
<div class="footer-content">
<div class="footer-section">
<h4>About <?php echo $site_name; ?></h4>
<p><?php echo $site_description; ?></p>
</div>
<div class="footer-section">
<h4>Quick Links</h4>
<ul>
<li><a href="about.php">About Us</a></li>
<li><a href="contact.php">Contact</a></li>
<li><a href="privacy-policy.php">Privacy Policy</a></li>
<li><a href="terms.php">Terms of Service</a></li>
</ul>
</div>
<div class="footer-section">
<h4>Categories</h4>
<ul>
<?php
$footer_categories = array_slice($categories, 0, 5);
foreach ($footer_categories as $category):
?>
<li><a href="category.php?slug=<?php echo $category['category_slug']; ?>">
<?php echo $category['category_name']; ?>
</a></li>
<?php endforeach; ?>
</ul>
</div>
<div class="footer-section">
<h4>Contact Info</h4>
<p><i class="fas fa-envelope"></i> <?php echo getSetting('contact_email', '[email protected]'); ?></p>
<p><i class="fas fa-phone"></i> <?php echo getSetting('contact_phone', '+1 234 567 890'); ?></p>
</div>
</div>
<div class="footer-bottom">
<p>© <?php echo date('Y'); ?> <?php echo $site_name; ?>. All rights reserved.</p>
</div>
</div>
</footer>
<!-- JavaScript Files -->
<script src="assets/js/main.js"></script>
<script src="assets/js/like-system.js"></script>
<script src="assets/js/share-system.js"></script>
<script src="assets/js/newsletter.js"></script>
<script>
// Initialize like system
document.addEventListener('DOMContentLoaded', function() {
initLikeSystem();
initShareSystem();
initNewsletter();
});
</script>
</body>
</html>
5. single.php (Single Post Page)
<?php
require_once 'includes/config.php';
require_once 'includes/functions.php';
// Get post slug from URL
$slug = isset($_GET['slug']) ? sanitize($_GET['slug']) : '';
if (empty($slug)) {
redirect('404.php');
}
// Get post details
$sql = "SELECT p.*, u.user_id, u.username, u.full_name, u.bio, u.avatar,
c.category_id, c.category_name, c.category_slug
FROM posts p
LEFT JOIN users u ON p.user_id = u.user_id
LEFT JOIN categories c ON p.category_id = c.category_id
WHERE p.slug = ? AND p.status = 'published'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("s", $slug);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
redirect('404.php');
}
$post = $result->fetch_assoc();
// Increment view count
incrementPostViews($post['post_id']);
// Get post tags
$sql = "SELECT t.* FROM tags t
JOIN post_tags pt ON t.tag_id = pt.tag_id
WHERE pt.post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post['post_id']);
$stmt->execute();
$tags = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get like count
$like_count = getPostLikes($post['post_id']);
// Get share count
$share_count = getPostShares($post['post_id']);
// Get comment count
$comment_count = getPostComments($post['post_id']);
// Check if user liked post
$user_liked = isLoggedIn() ? userLikedPost($post['post_id']) : false;
// Get related posts
$related_posts = getRelatedPosts($post['post_id'], $post['category_id'], 3);
// Get comments
$comment_page = isset($_GET['comment_page']) ? (int)$_GET['comment_page'] : 1;
$comment_offset = ($comment_page - 1) * COMMENTS_PER_PAGE;
$sql = "SELECT c.*, u.username, u.full_name, u.avatar
FROM comments c
LEFT JOIN users u ON c.user_id = u.user_id
WHERE c.post_id = ? AND c.status = 'approved' AND c.parent_comment_id IS NULL
ORDER BY c.created_at DESC
LIMIT ? OFFSET ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iii", $post['post_id'], COMMENTS_PER_PAGE, $comment_offset);
$stmt->execute();
$comments = $stmt->get_result()->fetch_all(MYSQLI_ASSOC);
// Get total comments count for pagination
$sql = "SELECT COUNT(*) as count FROM comments WHERE post_id = ? AND status = 'approved'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post['post_id']);
$stmt->execute();
$total_comments = $stmt->get_result()->fetch_assoc()['count'];
$total_comment_pages = ceil($total_comments / COMMENTS_PER_PAGE);
// Get share URLs
$share_urls = getShareUrls($post);
// Get site settings
$site_name = getSetting('site_name', 'BlogPlatform');
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title><?php echo $post['title']; ?> - <?php echo $site_name; ?></title>
<meta name="description" content="<?php echo $post['meta_description'] ?: truncateText(strip_tags($post['excerpt'] ?: $post['content']), 160); ?>">
<meta name="keywords" content="<?php echo $post['meta_keywords']; ?>">
<!-- Open Graph Tags -->
<meta property="og:title" content="<?php echo $post['title']; ?>">
<meta property="og:description" content="<?php echo truncateText(strip_tags($post['excerpt'] ?: $post['content']), 200); ?>">
<meta property="og:type" content="article">
<meta property="og:url" content="<?php echo SITE_URL . 'post/' . $post['slug']; ?>">
<meta property="og:image" content="<?php echo SITE_URL . 'uploads/posts/' . ($post['featured_image'] ?: 'default-post.jpg'); ?>">
<meta property="og:site_name" content="<?php echo $site_name; ?>">
<meta property="article:published_time" content="<?php echo $post['published_at']; ?>">
<meta property="article:author" content="<?php echo $post['full_name'] ?: $post['username']; ?>">
<?php foreach ($tags as $tag): ?>
<meta property="article:tag" content="<?php echo $tag['tag_name']; ?>">
<?php endforeach; ?>
<!-- Twitter Card Tags -->
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="<?php echo $post['title']; ?>">
<meta name="twitter:description" content="<?php echo truncateText(strip_tags($post['excerpt'] ?: $post['content']), 200); ?>">
<meta name="twitter:image" content="<?php echo SITE_URL . 'uploads/posts/' . ($post['featured_image'] ?: 'default-post.jpg'); ?>">
<link rel="stylesheet" href="assets/css/style.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
</head>
<body>
<!-- Navigation (same as index.php) -->
<nav class="navbar">
<!-- ... same navigation code ... -->
</nav>
<!-- Post Header -->
<header class="post-header">
<div class="container">
<div class="post-categories">
<a href="category.php?slug=<?php echo $post['category_slug']; ?>" class="post-category">
<?php echo $post['category_name']; ?>
</a>
</div>
<h1 class="post-title"><?php echo $post['title']; ?></h1>
<div class="post-meta-large">
<div class="post-author">
<img src="uploads/avatars/<?php echo $post['avatar'] ?: 'default-avatar.png'; ?>" alt="<?php echo $post['username']; ?>">
<div class="author-info">
<span class="author-name"><?php echo $post['full_name'] ?: $post['username']; ?></span>
<span class="post-date"><?php echo formatDate($post['published_at']); ?></span>
</div>
</div>
<div class="post-stats-large">
<span><i class="far fa-eye"></i> <?php echo number_format($post['views']); ?> views</span>
<span><i class="far fa-heart"></i> <span class="like-count"><?php echo $like_count; ?></span> likes</span>
<span><i class="far fa-comment"></i> <?php echo $comment_count; ?> comments</span>
<span><i class="far fa-share-square"></i> <?php echo $share_count; ?> shares</span>
<span><i class="far fa-clock"></i> <?php echo $post['reading_time'] ?: getReadingTime($post['content']); ?> min read</span>
</div>
</div>
</div>
</header>
<!-- Featured Image -->
<?php if ($post['featured_image']): ?>
<div class="featured-image-container">
<img src="uploads/posts/<?php echo $post['featured_image']; ?>" alt="<?php echo $post['title']; ?>" class="featured-image">
</div>
<?php endif; ?>
<!-- Main Content -->
<div class="main-content">
<div class="container">
<div class="content-wrapper">
<!-- Post Content -->
<main class="content-main">
<article class="single-post">
<div class="post-content">
<?php echo $post['content']; ?>
</div>
<!-- Post Tags -->
<?php if (!empty($tags)): ?>
<div class="post-tags">
<span>Tags:</span>
<?php foreach ($tags as $tag): ?>
<a href="search.php?tag=<?php echo $tag['tag_slug']; ?>" class="post-tag">
#<?php echo $tag['tag_name']; ?>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<!-- Like and Share Buttons -->
<div class="post-actions">
<button class="btn-like <?php echo $user_liked ? 'liked' : ''; ?>" data-post-id="<?php echo $post['post_id']; ?>">
<i class="fa<?php echo $user_liked ? 's' : 'r'; ?> fa-heart"></i>
<span class="like-text"><?php echo $user_liked ? 'Liked' : 'Like'; ?></span>
<span class="like-count">(<?php echo $like_count; ?>)</span>
</button>
<div class="share-buttons">
<span>Share:</span>
<a href="<?php echo $share_urls['facebook']; ?>" target="_blank" class="share-btn facebook" data-platform="facebook">
<i class="fab fa-facebook-f"></i>
</a>
<a href="<?php echo $share_urls['twitter']; ?>" target="_blank" class="share-btn twitter" data-platform="twitter">
<i class="fab fa-twitter"></i>
</a>
<a href="<?php echo $share_urls['linkedin']; ?>" target="_blank" class="share-btn linkedin" data-platform="linkedin">
<i class="fab fa-linkedin-in"></i>
</a>
<a href="<?php echo $share_urls['whatsapp']; ?>" target="_blank" class="share-btn whatsapp" data-platform="whatsapp">
<i class="fab fa-whatsapp"></i>
</a>
<button class="share-btn copy-link" data-url="<?php echo SITE_URL . 'post/' . $post['slug']; ?>">
<i class="far fa-copy"></i>
</button>
</div>
</div>
<!-- Author Bio -->
<div class="author-bio">
<img src="uploads/avatars/<?php echo $post['avatar'] ?: 'default-avatar.png'; ?>" alt="<?php echo $post['username']; ?>">
<div class="author-bio-content">
<h4>About <?php echo $post['full_name'] ?: $post['username']; ?></h4>
<p><?php echo $post['bio'] ?: 'No bio provided.'; ?></p>
<div class="author-links">
<a href="author.php?username=<?php echo $post['username']; ?>" class="btn-small">
View All Posts
</a>
</div>
</div>
</div>
<!-- Related Posts -->
<?php if (!empty($related_posts)): ?>
<section class="related-posts">
<h3>Related Posts</h3>
<div class="related-grid">
<?php foreach ($related_posts as $related): ?>
<div class="related-card">
<a href="post.php?slug=<?php echo $related['slug']; ?>">
<img src="uploads/posts/<?php echo $related['featured_image'] ?: 'default-post-thumb.jpg'; ?>"
alt="<?php echo $related['title']; ?>">
<h4><?php echo truncateText($related['title'], 60); ?></h4>
</a>
</div>
<?php endforeach; ?>
</div>
</section>
<?php endif; ?>
<!-- Comments Section -->
<section class="comments-section">
<h3>Comments (<?php echo $total_comments; ?>)</h3>
<!-- Comment Form -->
<div class="comment-form-wrapper">
<h4>Leave a Comment</h4>
<?php if (isLoggedIn()): ?>
<form id="comment-form" class="comment-form">
<input type="hidden" name="post_id" value="<?php echo $post['post_id']; ?>">
<input type="hidden" name="parent_id" value="0" id="parent-comment-id">
<div class="form-group">
<textarea name="comment" rows="5" placeholder="Write your comment..." required></textarea>
</div>
<button type="submit" class="btn btn-primary">Post Comment</button>
<button type="button" class="btn-cancel-reply" style="display: none;">Cancel Reply</button>
</form>
<?php else: ?>
<p>Please <a href="user/login.php">login</a> to leave a comment.</p>
<?php endif; ?>
</div>
<!-- Comments List -->
<div class="comments-list" id="comments-list">
<?php foreach ($comments as $comment): ?>
<?php include 'includes/comment.php'; ?>
<?php endforeach; ?>
</div>
<!-- Comments Pagination -->
<?php if ($total_comment_pages > 1): ?>
<div class="comments-pagination">
<?php
$comment_url_pattern = SITE_URL . 'post/' . $post['slug'] . '?comment_page={page}';
echo getPagination($comment_page, $total_comment_pages, $comment_url_pattern);
?>
</div>
<?php endif; ?>
</section>
</article>
</main>
<!-- Sidebar (same as index.php) -->
<aside class="content-sidebar">
<!-- ... same sidebar code ... -->
</aside>
</div>
</div>
</div>
<!-- Footer (same as index.php) -->
<footer class="footer">
<!-- ... same footer code ... -->
</footer>
<!-- JavaScript Files -->
<script src="assets/js/main.js"></script>
<script src="assets/js/like-system.js"></script>
<script src="assets/js/share-system.js"></script>
<script src="assets/js/comment-system.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
initLikeSystem();
initShareSystem();
initCommentSystem(<?php echo $post['post_id']; ?>);
});
</script>
</body>
</html>
6. api/like.php (AJAX Like Handler)
<?php
require_once '../includes/config.php';
require_once '../includes/functions.php';
header('Content-Type: application/json');
// Check if request is POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['success' => false, 'message' => 'Invalid request method'], 405);
}
// Get POST data
$post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0;
if (!$post_id) {
jsonResponse(['success' => false, 'message' => 'Invalid post ID']);
}
// Check if post exists
$sql = "SELECT post_id FROM posts WHERE post_id = ? AND status = 'published'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
if ($stmt->get_result()->num_rows === 0) {
jsonResponse(['success' => false, 'message' => 'Post not found']);
}
// Check if user is logged in
if (!isLoggedIn()) {
jsonResponse(['success' => false, 'message' => 'Please login to like posts']);
}
$user_id = $_SESSION['user_id'];
// Check if already liked
$sql = "SELECT like_id FROM likes WHERE post_id = ? AND user_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $post_id, $user_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows > 0) {
// Unlike
$sql = "DELETE FROM likes WHERE post_id = ? AND user_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $post_id, $user_id);
$action = 'unliked';
if ($stmt->execute()) {
// Get updated like count
$like_count = getPostLikes($post_id);
// Log activity
logActivity($user_id, 'unlike', 'Unliked post ID: ' . $post_id);
jsonResponse([
'success' => true,
'action' => 'unliked',
'like_count' => $like_count,
'message' => 'Post unliked'
]);
}
} else {
// Like
$sql = "INSERT INTO likes (post_id, user_id) VALUES (?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $post_id, $user_id);
$action = 'liked';
if ($stmt->execute()) {
// Get updated like count
$like_count = getPostLikes($post_id);
// Create notification for post author
$sql = "SELECT user_id FROM posts WHERE post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$post_author = $stmt->get_result()->fetch_assoc();
if ($post_author['user_id'] != $user_id) {
$message = $_SESSION['username'] . ' liked your post';
createNotification($post_author['user_id'], 'like', $post_id, 'post', $message);
}
// Log activity
logActivity($user_id, 'like', 'Liked post ID: ' . $post_id);
jsonResponse([
'success' => true,
'action' => 'liked',
'like_count' => $like_count,
'message' => 'Post liked'
]);
}
}
jsonResponse(['success' => false, 'message' => 'Failed to process like']);
?>
7. api/share.php (Share Tracking Handler)
<?php
require_once '../includes/config.php';
require_once '../includes/functions.php';
header('Content-Type: application/json');
// Check if request is POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['success' => false, 'message' => 'Invalid request method'], 405);
}
// Get POST data
$post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0;
$platform = isset($_POST['platform']) ? sanitize($_POST['platform']) : '';
if (!$post_id || !$platform) {
jsonResponse(['success' => false, 'message' => 'Invalid parameters']);
}
// Validate platform
$valid_platforms = ['facebook', 'twitter', 'linkedin', 'whatsapp', 'email', 'copy'];
if (!in_array($platform, $valid_platforms)) {
jsonResponse(['success' => false, 'message' => 'Invalid platform']);
}
// Check if post exists
$sql = "SELECT post_id FROM posts WHERE post_id = ? AND status = 'published'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
if ($stmt->get_result()->num_rows === 0) {
jsonResponse(['success' => false, 'message' => 'Post not found']);
}
// Record share
$user_id = isLoggedIn() ? $_SESSION['user_id'] : null;
$ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$sql = "INSERT INTO shares (post_id, user_id, platform, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iisss", $post_id, $user_id, $platform, $ip_address, $user_agent);
if ($stmt->execute()) {
// Get updated share count
$share_count = getPostShares($post_id);
// Log activity if user is logged in
if ($user_id) {
logActivity($user_id, 'share', 'Shared post ID: ' . $post_id . ' on ' . $platform);
}
jsonResponse([
'success' => true,
'share_count' => $share_count,
'message' => 'Share recorded'
]);
}
jsonResponse(['success' => false, 'message' => 'Failed to record share']);
?>
8. api/comment.php (AJAX Comment Handler)
<?php
require_once '../includes/config.php';
require_once '../includes/functions.php';
header('Content-Type: application/json');
// Check if request is POST
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
jsonResponse(['success' => false, 'message' => 'Invalid request method'], 405);
}
// Check if user is logged in
if (!isLoggedIn()) {
jsonResponse(['success' => false, 'message' => 'Please login to comment']);
}
// Get POST data
$post_id = isset($_POST['post_id']) ? (int)$_POST['post_id'] : 0;
$parent_id = isset($_POST['parent_id']) ? (int)$_POST['parent_id'] : 0;
$comment = isset($_POST['comment']) ? trim($_POST['comment']) : '';
if (!$post_id || empty($comment)) {
jsonResponse(['success' => false, 'message' => 'Invalid parameters']);
}
// Check if post exists
$sql = "SELECT post_id, user_id FROM posts WHERE post_id = ? AND status = 'published'";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $post_id);
$stmt->execute();
$result = $stmt->get_result();
if ($result->num_rows === 0) {
jsonResponse(['success' => false, 'message' => 'Post not found']);
}
$post = $result->fetch_assoc();
// Check if parent comment exists
if ($parent_id) {
$sql = "SELECT comment_id FROM comments WHERE comment_id = ? AND post_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("ii", $parent_id, $post_id);
$stmt->execute();
if ($stmt->get_result()->num_rows === 0) {
jsonResponse(['success' => false, 'message' => 'Parent comment not found']);
}
}
// Insert comment
$user_id = $_SESSION['user_id'];
$status = getSetting('comment_moderation', '1') == '1' ? 'pending' : 'approved';
$ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
$sql = "INSERT INTO comments (post_id, user_id, parent_comment_id, comment, status, ip_address, user_agent)
VALUES (?, ?, ?, ?, ?, ?, ?)";
$stmt = $conn->prepare($sql);
$stmt->bind_param("iiissss", $post_id, $user_id, $parent_id, $comment, $status, $ip_address, $user_agent);
if ($stmt->execute()) {
$comment_id = $conn->insert_id;
// Create notification for post author
if ($post['user_id'] != $user_id) {
$message = $_SESSION['username'] . ' commented on your post';
createNotification($post['user_id'], 'comment', $post_id, 'post', $message);
}
// Create notification for parent comment author
if ($parent_id) {
$sql = "SELECT user_id FROM comments WHERE comment_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $parent_id);
$stmt->execute();
$parent = $stmt->get_result()->fetch_assoc();
if ($parent['user_id'] && $parent['user_id'] != $user_id) {
$message = $_SESSION['username'] . ' replied to your comment';
createNotification($parent['user_id'], 'reply', $comment_id, 'comment', $message);
}
}
// Log activity
logActivity($user_id, 'comment', 'Commented on post ID: ' . $post_id);
// Get comment HTML if approved
$comment_html = '';
if ($status == 'approved') {
$sql = "SELECT c.*, u.username, u.full_name, u.avatar
FROM comments c
LEFT JOIN users u ON c.user_id = u.user_id
WHERE c.comment_id = ?";
$stmt = $conn->prepare($sql);
$stmt->bind_param("i", $comment_id);
$stmt->execute();
$new_comment = $stmt->get_result()->fetch_assoc();
ob_start();
include '../includes/comment.php';
$comment_html = ob_get_clean();
}
jsonResponse([
'success' => true,
'comment_id' => $comment_id,
'status' => $status,
'comment_html' => $comment_html,
'message' => $status == 'pending' ? 'Your comment is pending moderation' : 'Comment posted successfully'
]);
}
jsonResponse(['success' => false, 'message' => 'Failed to post comment']);
?>
9. assets/js/like-system.js
// Like System
function initLikeSystem() {
// Like buttons
document.querySelectorAll('.btn-like').forEach(button => {
button.addEventListener('click', handleLike);
});
}
function handleLike(e) {
e.preventDefault();
const button = e.currentTarget;
const postId = button.dataset.postId;
// Disable button during request
button.disabled = true;
// Send like request
fetch('api/like.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'post_id=' + postId
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update button state
if (data.action === 'liked') {
button.classList.add('liked');
button.querySelector('i').className = 'fas fa-heart';
button.querySelector('.like-text').textContent = 'Liked';
} else {
button.classList.remove('liked');
button.querySelector('i').className = 'far fa-heart';
button.querySelector('.like-text').textContent = 'Like';
}
// Update like count
const likeCountElements = document.querySelectorAll(`.like-count[data-post-id="${postId}"], .like-count:not([data-post-id])`);
likeCountElements.forEach(el => {
if (el.closest(`[data-post-id="${postId}"]`) || !el.dataset.postId) {
el.textContent = data.like_count;
}
});
// Show success message
showNotification(data.message, 'success');
} else {
showNotification(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('An error occurred. Please try again.', 'error');
})
.finally(() => {
button.disabled = false;
});
}
// Like comment
function likeComment(commentId) {
fetch('api/comment-like.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'comment_id=' + commentId
})
.then(response => response.json())
.then(data => {
if (data.success) {
const likeButton = document.querySelector(`.comment-like[data-comment-id="${commentId}"]`);
const likeCount = document.querySelector(`.comment-like-count[data-comment-id="${commentId}"]`);
if (data.action === 'liked') {
likeButton.classList.add('liked');
likeButton.querySelector('i').className = 'fas fa-heart';
likeButton.querySelector('.like-text').textContent = 'Liked';
} else {
likeButton.classList.remove('liked');
likeButton.querySelector('i').className = 'far fa-heart';
likeButton.querySelector('.like-text').textContent = 'Like';
}
likeCount.textContent = data.like_count;
} else {
showNotification(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('An error occurred. Please try again.', 'error');
});
}
10. assets/js/share-system.js
// Share System
function initShareSystem() {
// Share buttons
document.querySelectorAll('.share-btn').forEach(button => {
if (button.classList.contains('copy-link')) {
button.addEventListener('click', handleCopyLink);
} else {
button.addEventListener('click', handleShare);
}
});
}
function handleShare(e) {
e.preventDefault();
const button = e.currentTarget;
const url = button.href;
const platform = button.dataset.platform;
const postId = getPostId();
// Open share dialog
window.open(url, 'share', 'width=600,height=400');
// Track share
if (postId && platform) {
trackShare(postId, platform);
}
}
function handleCopyLink(e) {
e.preventDefault();
const button = e.currentTarget;
const url = button.dataset.url;
const postId = getPostId();
// Copy to clipboard
navigator.clipboard.writeText(url).then(() => {
showNotification('Link copied to clipboard!', 'success');
// Track share
if (postId) {
trackShare(postId, 'copy');
}
}).catch(() => {
showNotification('Failed to copy link', 'error');
});
}
function trackShare(postId, platform) {
fetch('api/share.php', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: 'post_id=' + postId + '&platform=' + platform
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Update share count if needed
const shareCount = document.querySelector('.share-count');
if (shareCount) {
shareCount.textContent = data.share_count;
}
}
})
.catch(error => {
console.error('Error tracking share:', error);
});
}
function getPostId() {
const likeButton = document.querySelector('.btn-like');
return likeButton ? likeButton.dataset.postId : null;
}
11. assets/js/comment-system.js
// Comment System
function initCommentSystem(postId) {
this.postId = postId;
// Comment form
const commentForm = document.getElementById('comment-form');
if (commentForm) {
commentForm.addEventListener('submit', handleCommentSubmit);
}
// Reply buttons
document.querySelectorAll('.reply-btn').forEach(button => {
button.addEventListener('click', handleReplyClick);
});
// Cancel reply button
const cancelBtn = document.querySelector('.btn-cancel-reply');
if (cancelBtn) {
cancelBtn.addEventListener('click', cancelReply);
}
// Load more comments
const loadMoreBtn = document.getElementById('load-more-comments');
if (loadMoreBtn) {
loadMoreBtn.addEventListener('click', loadMoreComments);
}
}
function handleCommentSubmit(e) {
e.preventDefault();
const form = e.currentTarget;
const formData = new FormData(form);
const submitBtn = form.querySelector('button[type="submit"]');
// Disable button during submission
submitBtn.disabled = true;
submitBtn.textContent = 'Posting...';
fetch('api/comment.php', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Clear form
form.reset();
// Hide reply form if replying
const replyForm = document.querySelector('.reply-form');
if (replyForm) {
replyForm.remove();
}
// Show cancel reply button
document.querySelector('.btn-cancel-reply').style.display = 'none';
// Reset parent comment ID
document.getElementById('parent-comment-id').value = '0';
// Add comment to list if approved
if (data.status === 'approved' && data.comment_html) {
const commentsList = document.getElementById('comments-list');
if (commentsList) {
commentsList.insertAdjacentHTML('afterbegin', data.comment_html);
}
}
showNotification(data.message, 'success');
} else {
showNotification(data.message, 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('An error occurred. Please try again.', 'error');
})
.finally(() => {
submitBtn.disabled = false;
submitBtn.textContent = 'Post Comment';
});
}
function handleReplyClick(e) {
e.preventDefault();
const button = e.currentTarget;
const commentId = button.dataset.commentId;
const authorName = button.dataset.author;
// Remove any existing reply forms
const existingForms = document.querySelectorAll('.reply-form');
existingForms.forEach(form => form.remove());
// Create reply form
const replyForm = document.createElement('div');
replyForm.className = 'reply-form';
replyForm.innerHTML = `
<h5>Reply to ${authorName}</h5>
<form class="comment-form">
<input type="hidden" name="post_id" value="${postId}">
<input type="hidden" name="parent_id" value="${commentId}">
<div class="form-group">
<textarea name="comment" rows="3" placeholder="Write your reply..." required></textarea>
</div>
<button type="submit" class="btn btn-small btn-primary">Post Reply</button>
<button type="button" class="btn btn-small btn-cancel-reply">Cancel</button>
</form>
`;
// Insert after the comment
const commentElement = button.closest('.comment');
commentElement.parentNode.insertBefore(replyForm, commentElement.nextSibling);
// Show cancel reply button
document.querySelector('.btn-cancel-reply').style.display = 'inline-block';
// Add form submit handler
replyForm.querySelector('form').addEventListener('submit', handleCommentSubmit);
replyForm.querySelector('.btn-cancel-reply').addEventListener('click', () => {
replyForm.remove();
document.querySelector('.btn-cancel-reply').style.display = 'none';
});
}
function cancelReply() {
const replyForms = document.querySelectorAll('.reply-form');
replyForms.forEach(form => form.remove());
document.querySelector('.btn-cancel-reply').style.display = 'none';
document.getElementById('parent-comment-id').value = '0';
}
function loadMoreComments(e) {
e.preventDefault();
const button = e.currentTarget;
const page = parseInt(button.dataset.page) + 1;
const postId = button.dataset.postId;
button.disabled = true;
button.textContent = 'Loading...';
fetch(`api/get-comments.php?post_id=${postId}&page=${page}`)
.then(response => response.json())
.then(data => {
if (data.success && data.comments.length > 0) {
const commentsList = document.getElementById('comments-list');
data.comments.forEach(comment => {
commentsList.insertAdjacentHTML('beforeend', comment.html);
});
button.dataset.page = page;
if (data.has_more) {
button.disabled = false;
button.textContent = 'Load More Comments';
} else {
button.remove();
}
// Reinitialize reply buttons for new comments
document.querySelectorAll('.reply-btn').forEach(btn => {
btn.addEventListener('click', handleReplyClick);
});
} else {
button.remove();
}
})
.catch(error => {
console.error('Error:', error);
button.disabled = false;
button.textContent = 'Load More Comments';
showNotification('Failed to load comments', 'error');
});
}
12. assets/js/main.js (Utility Functions)
// Utility Functions
// Show notification
function showNotification(message, type = 'info') {
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.textContent = message;
notification.style.cssText = `
position: fixed;
top: 20px;
right: 20px;
padding: 15px 25px;
background: ${type === 'success' ? '#4cc9f0' : type === 'error' ? '#f72585' : '#4361ee'};
color: white;
border-radius: 8px;
z-index: 9999;
animation: slideIn 0.3s ease;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.animation = 'slideOut 0.3s ease';
setTimeout(() => {
notification.remove();
}, 300);
}, 3000);
}
// Debounce function
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Format date
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
}
// Time ago
function timeAgo(dateString) {
const date = new Date(dateString);
const now = new Date();
const seconds = Math.floor((now - date) / 1000);
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
for (let [unit, secondsInUnit] of Object.entries(intervals)) {
const interval = Math.floor(seconds / secondsInUnit);
if (interval >= 1) {
return interval + ' ' + unit + (interval > 1 ? 's' : '') + ' ago';
}
}
return 'just now';
}
// Smooth scroll
function smoothScroll(target) {
const element = document.querySelector(target);
if (element) {
element.scrollIntoView({
behavior: 'smooth',
block: 'start'
});
}
}
// Toggle class
function toggleClass(element, className) {
if (element.classList.contains(className)) {
element.classList.remove(className);
} else {
element.classList.add(className);
}
}
// Get URL parameters
function getUrlParams() {
const params = new URLSearchParams(window.location.search);
const result = {};
for (let [key, value] of params.entries()) {
result[key] = value;
}
return result;
}
// Set URL parameter
function setUrlParam(key, value) {
const url = new URL(window.location.href);
url.searchParams.set(key, value);
window.history.pushState({}, '', url);
}
// Delete URL parameter
function deleteUrlParam(key) {
const url = new URL(window.location.href);
url.searchParams.delete(key);
window.history.pushState({}, '', url);
}
// Responsive navigation
function initResponsiveNav() {
const menuToggle = document.querySelector('.menu-toggle');
const navMenu = document.querySelector('.nav-menu');
if (menuToggle) {
menuToggle.addEventListener('click', () => {
navMenu.classList.toggle('active');
menuToggle.classList.toggle('active');
});
}
}
// Back to top button
function initBackToTop() {
const backToTop = document.getElementById('back-to-top');
if (backToTop) {
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
backToTop.classList.add('visible');
} else {
backToTop.classList.remove('visible');
}
});
backToTop.addEventListener('click', (e) => {
e.preventDefault();
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
}
// Initialize on DOM load
document.addEventListener('DOMContentLoaded', () => {
initResponsiveNav();
initBackToTop();
// Add animation on scroll
const animatedElements = document.querySelectorAll('.animate-on-scroll');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animated');
observer.unobserve(entry.target);
}
});
}, {
threshold: 0.1
});
animatedElements.forEach(el => observer.observe(el));
});
// Add CSS animations
const style = document.createElement('style');
style.textContent = `
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOut {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(100%);
opacity: 0;
}
}
.animate-on-scroll {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.6s ease, transform 0.6s ease;
}
.animate-on-scroll.animated {
opacity: 1;
transform: translateY(0);
}
`;
document.head.appendChild(style);
How to Use This Project - Step by Step Guide
Step 1: System Requirements
- XAMPP/WAMP/MAMP (PHP 7.4+)
- MySQL 5.7+
- Web browser with JavaScript enabled
- 1GB RAM minimum
- 500MB free disk space
Step 2: Installation
- Download and Extract
# Navigate to htdocs (XAMPP) or www (WAMP) cd C:\xampp\htdocs\ # Create project folder mkdir blog-platform # Extract all files into this folder
- Database Setup
- Open phpMyAdmin (http://localhost/phpmyadmin)
- Create new database:
blog_platform - Import
database/blog_platform.sql - Verify all tables are created
- Configure Project
- Open
includes/config.php - Update database credentials if needed:
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_platform');
- Update site URL:
define('SITE_URL', 'http://localhost/blog-platform/');
- Set Folder Permissions
# Create required directories mkdir uploads mkdir uploads/posts mkdir uploads/avatars mkdir uploads/categories mkdir cache # Set permissions (Linux/Mac) chmod -R 755 uploads/ chmod -R 755 cache/ # Windows: Ensure write permissions for these folders
Step 3: Initial Configuration
- Default Admin Account
- Username: admin
- Password: Admin@123
- Email: [email protected]
- Access Admin Panel
- Go to:
http://localhost/blog-platform/admin/login.php - Login with admin credentials
- Update site settings:
- Site name and description
- Social media links
- Email settings
- SEO settings
Step 4: Testing Features
User Features:
- Registration & Login
- Click "Register" on homepage
- Fill registration form
- Verify email (if enabled)
- Login with credentials
- Browse Posts
- View homepage with featured posts
- Browse categories
- Search for posts
- Filter by tags
- Read Posts
- Click on any post to read
- View author information
- Check related posts
- See post statistics
- Like System
- Click like button on any post
- See like count update in real-time
- Unlike to remove like
- View liked posts in dashboard
- Share System
- Use share buttons on posts
- Share on social media
- Copy link to clipboard
- Share count tracking
- Comment System
- Leave comments on posts
- Reply to other comments
- See comment moderation (if enabled)
- Receive notifications for replies
- User Dashboard
- View profile
- Manage own posts
- See liked posts
- View notifications
- Update settings
Author Features:
- Create Posts
- Go to dashboard
- Click "Add New Post"
- Use rich text editor
- Add featured image
- Set categories and tags
- Save as draft or publish
- Manage Posts
- Edit existing posts
- Delete posts
- View post statistics
- Track engagement
Admin Features:
- Dashboard
- View site statistics
- Monitor user activity
- See recent posts
- Check pending comments
- User Management
- View all users
- Add/edit users
- Change user roles
- Delete users
- Reset passwords
- Post Management
- View all posts
- Edit any post
- Delete inappropriate content
- Feature/unfeature posts
- Category Management
- Add categories
- Edit categories
- Delete categories
- Set parent categories
- Comment Moderation
- Approve comments
- Mark as spam
- Delete comments
- Manage replies
- Settings
- Configure site settings
- Set comment moderation
- Update social links
- SEO configuration
- Email settings
Step 5: Customization
- Theme Customization
- Edit
assets/css/style.css - Modify colors and styles
- Update logo and favicon
- Customize layout
- Functionality Extensions
- Add social login
- Implement email notifications
- Add