A modern, clean PHP blog system with admin panel, built using PHP (MySQLi) and vanilla JavaScript.
Blog_System/
├── admin/ # Admin Panel
│ ├── add-article.php # Create new article
│ ├── comments.php # Manage article comments
│ ├── edit-article.php # Edit existing article
│ ├── index.php # Admin dashboard
│ ├── login.php # Admin login
│ ├── logout.php # Admin logout
│ ├── messages.php # Contact form messages
│ ├── settings.php # Site settings (About, Contact info)
│ └── upload-image.php # TinyMCE image upload handler
├── css/
│ ├── admin.css # Admin panel styles
│ ├── frontend.css # Frontend styles (main)
│ └── style.css # Additional styles
├── gif/
│ ├── blog_system_logo.svg # Site logo
│ └── favicon.ico # Favicon
├── includes/
│ ├── auth.php # Admin authentication
│ ├── config.php # Database & site configuration
│ ├── footer.php # Shared footer
│ └── header.php # Shared header
├── js/
│ ├── admin.js # Admin panel JavaScript
│ └── main.js # Frontend JavaScript
├── uploads/ # Uploaded images
├── .htaccess # Apache URL rewriting & security
├── 403.php # Forbidden error page
├── 404.php # Not found error page
├── about.php # About page
├── article.php # Single article view
├── articles.php # All articles listing (paginated)
├── contact.php # Contact form page
├── database.sql # Database schema
├── index.php # Homepage
├── most-read.php # Most viewed articles
└── README.md # This file
- XAMPP (or similar: PHP 7.4+, MySQL 5.7+, Apache with mod_rewrite)
- Web browser
-
Copy files to
c:\xampp\htdocs\Blog_System\ -
Create the database:
- Open phpMyAdmin
- Create database:
blog_system - Import
database.sqlfile
-
Enable mod_rewrite (for clean URLs):
- Open
C:\xampp\apache\conf\httpd.conf - Find
#LoadModule rewrite_module modules/mod_rewrite.so - Remove the
#to uncomment it - Ensure
AllowOverride Allis set for htdocs directory - Restart Apache
- Open
-
Configure (optional):
- Edit
includes/config.phpto change site name or URL
- Edit
-
Access the site:
- Frontend: http://localhost/Blog_System/
- Admin: http://localhost/Blog_System/admin/
- Responsive design with mobile navigation
- Article listing with pagination
- Single article view with comments
- Most read articles page
- About and Contact pages
- Clean URLs for articles (
/article/my-article-slug) - SEO-friendly meta tags
- Creative fonts (Playfair Display + Poppins)
- Dashboard with statistics
- Article CRUD operations
- TinyMCE rich text editor with image upload
- Comment moderation (approve/hide/delete)
- Contact message management
- Site settings (About page, Contact info)
- Toggle article visibility
Articles use clean, SEO-friendly URLs powered by Apache mod_rewrite:
| Clean URL | Internal |
|---|---|
/article/my-post |
article.php?url=my-post |
.htaccess rule:
RewriteEngine On
RewriteBase /Blog_System/
# Clean URLs for articles: /article/slug -> article.php?url=slug
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^article/([^/]+)/?$ article.php?url=$1 [L,QSA]The slug (article_url) is a URL-friendly identifier for each article.
Creation (admin/add-article.php):
// Clean URL (make it URL friendly)
$article_url = preg_replace('/[^a-zA-Z0-9-]/', '-', strtolower($article_url));
$article_url = preg_replace('/-+/', '-', $article_url); // Remove multiple dashes
$article_url = trim($article_url, '-'); // Remove leading/trailing dashesFetching (article.php):
// Get article URL from query string
$article_url = isset($_GET['url']) ? trim($_GET['url']) : '';
// Fetch article by slug
$stmt = mysqli_prepare($conn, "SELECT * FROM articles WHERE article_url = ? AND is_visible = 1");
mysqli_stmt_bind_param($stmt, 's', $article_url);
mysqli_stmt_execute($stmt);Usage in links:
<a href="article/<?php echo urlencode($article['article_url']); ?>">
<?php echo htmlspecialchars($article['article_title']); ?>
</a>The mobile hamburger menu uses JavaScript to toggle a CSS class.
HTML Structure (all pages):
<button class="menu-toggle" onclick="toggleMenu()">
<span></span><span></span><span></span>
</button>
<ul class="nav-menu" id="navMenu">
<li><a href="index.php">Home</a></li>
<!-- more links -->
</ul>JavaScript (js/main.js):
function toggleMenu() {
var menu = document.getElementById('navMenu');
menu.classList.toggle('active');
}
// Close menu when clicking outside
document.addEventListener('click', function(e) {
var menu = document.getElementById('navMenu');
var toggle = document.querySelector('.menu-toggle');
if (menu && toggle) {
if (!menu.contains(e.target) && !toggle.contains(e.target)) {
menu.classList.remove('active');
}
}
});SEO meta tags are stored per article and rendered in the <head> section.
Database Schema (articles table):
meta_title VARCHAR(255) NOT NULL,
meta_description TEXT,
meta_keywords VARCHAR(255),Fetching & Rendering (article.php):
// Fetch article (includes meta fields)
$article = mysqli_fetch_assoc($result);
// Set page meta data
$page_title = $article['meta_title'] . ' - ' . SITE_NAME;
$meta_description = $article['meta_description'];
$meta_keywords = $article['meta_keywords'];HTML Output:
<head>
<meta name="description" content="<?php echo htmlspecialchars($meta_description); ?>">
<meta name="keywords" content="<?php echo htmlspecialchars($meta_keywords); ?>">
<title><?php echo htmlspecialchars($page_title); ?></title>
</head>Featured Image (Thumbnail):
- Uploaded via file input in admin
- Stored in
uploads/folder - Filename saved in
articles.featured_imagecolumn - Displayed on article cards (listing pages)
Content Images:
- Uploaded via TinyMCE editor
- Handled by
admin/upload-image.php - Stored in
uploads/folder - Embedded in
article_descriptionas HTML<img>tags
Increment on Page Load (article.php):
// Increment view counter
mysqli_query($conn, "UPDATE articles SET user_views = user_views + 1 WHERE id = " . $article['id']);
$article['user_views']++; // Update local variable for displaySubmission Flow:
- User submits comment form
- Comment saved with
is_approved = 0(pending) - Redirect with success message (PRG pattern)
- Admin approves in dashboard
- Only approved comments shown on frontend
PRG Pattern (Post-Redirect-Get):
if (mysqli_stmt_execute($stmt)) {
// Redirect to prevent form resubmission
header('Location: article/' . urlencode($article_url) . '?comment=success#comments');
exit;
}| Column | Type | Description |
|---|---|---|
| id | INT | Primary key |
| article_url | VARCHAR(255) | URL slug (unique) |
| meta_title | VARCHAR(255) | SEO title |
| meta_description | TEXT | SEO description |
| meta_keywords | VARCHAR(255) | SEO keywords (comma-separated) |
| article_title | VARCHAR(255) | Display title |
| article_description | LONGTEXT | Article content (HTML) |
| featured_image | VARCHAR(255) | Thumbnail filename |
| is_visible | TINYINT(1) | 1=visible, 0=hidden |
| user_views | INT | View counter |
| created_at | TIMESTAMP | Creation date |
| updated_at | TIMESTAMP | Last update date |
| Column | Type | Description |
|---|---|---|
| id | INT | Primary key |
| article_id | INT | Foreign key to articles |
| user_name | VARCHAR(100) | Commenter name |
| user_email | VARCHAR(150) | Commenter email (optional) |
| comment_text | TEXT | Comment content |
| is_approved | TINYINT(1) | 1=approved, 0=pending |
| created_at | TIMESTAMP | Submission date |
| Column | Type | Description |
|---|---|---|
| id | INT | Primary key |
| setting_key | VARCHAR(100) | Setting identifier |
| setting_value | LONGTEXT | Setting content |
| updated_at | TIMESTAMP | Last update date |
Settings Keys:
about_title- About page titleabout_content- About page HTML contentcontact_email- Contact emailcontact_phone- Contact phonecontact_address- Contact address
| Column | Type | Description |
|---|---|---|
| id | INT | Primary key |
| sender_name | VARCHAR(100) | Sender name |
| sender_email | VARCHAR(150) | Sender email |
| subject | VARCHAR(255) | Message subject |
| message | TEXT | Message content |
| is_read | TINYINT(1) | 1=read, 0=unread |
| created_at | TIMESTAMP | Submission date |
// Database
define('DB_HOST', 'localhost');
define('DB_USER', 'root');
define('DB_PASS', '');
define('DB_NAME', 'blog_system');
// Site
define('SITE_NAME', 'Simple Blog System');
define('SITE_URL', 'http://localhost/Blog_System/');The .htaccess file provides several security measures:
- Directory listing disabled - Prevents browsing folder contents
- Protected sensitive files - Blocks access to config.php, auth.php
- Protected includes folder - Returns 403 for /includes/ requests
- Hidden files blocked - Files starting with
.are inaccessible - Backup files blocked -
.bak,.sql,.logfiles blocked - Security headers - X-Content-Type-Options, X-Frame-Options, X-XSS-Protection
The site uses Google Fonts:
- Playfair Display - Headings (elegant serif)
- Poppins - Body text (clean sans-serif)
- Navbar Background:
#3d4354 - Primary Blue:
#2563eb - Text Dark:
#111827 - Text Medium:
#4b5563 - Text Light:
#6b7280