├── .idea ├── .gitignore ├── misc.xml └── vcs.xml ├── LICENSE ├── README.md ├── config.php ├── create_post.php ├── database └── schema.sql ├── delete_post.php ├── edit_post.php ├── inc ├── footer.php └── header.php ├── index.php └── view_post.php /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Son Nguyen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # PHP Blog CMS 2 | 3 | A simple Blog Content Management System (CMS) built using PHP and MySQL. This application provides a basic web interface for creating, reading, updating, and deleting (CRUD) blog posts. It is designed for learning and development purposes, and can be extended with features like user authentication, comments, and an administrative dashboard. 4 | 5 | ## Table of Contents 6 | 7 | 1. [Overview](#overview) 8 | 2. [Features](#features) 9 | 3. [Architecture & Design](#architecture--design) 10 | 4. [Tech Stack](#tech-stack) 11 | 5. [Installation & Setup](#installation--setup) 12 | - [Prerequisites](#prerequisites) 13 | - [Local Setup](#local-setup) 14 | - [Environment Configuration](#environment-configuration) 15 | 6. [Database Schema](#database-schema) 16 | 7. [File Structure](#file-structure) 17 | 8. [Usage](#usage) 18 | 9. [Testing](#testing) 19 | 10. [Deployment](#deployment) 20 | - [Apache/Nginx Setup](#apachenignx-setup) 21 | - [Docker Deployment](#docker-deployment) 22 | 11. [Security Considerations](#security-considerations) 23 | 12. [Future Enhancements](#future-enhancements) 24 | 13. [Troubleshooting & FAQ](#troubleshooting--faq) 25 | 14. [Contributing](#contributing) 26 | 15. [License](#license) 27 | 28 | --- 29 | 30 | ## Overview 31 | 32 | The PHP Blog CMS is a lightweight content management system that allows users to manage blog posts through a simple web interface. It demonstrates the basics of PHP with SQL persistence using PDO and MySQL. The application covers core CRUD operations and provides a foundation to expand with advanced features like user authentication, search, and SEO-friendly URLs. 33 | 34 | --- 35 | 36 | ## Features 37 | 38 | - **CRUD Operations:** Create, read, update, and delete blog posts. 39 | - **Responsive UI:** Built with Bootstrap for a mobile-friendly experience. 40 | - **Simple Routing:** Easy-to-understand PHP scripts for each operation. 41 | - **Database Integration:** Uses MySQL with PDO for secure database interactions. 42 | - **Extensible:** Serves as a foundation for adding features like comments and user management. 43 | 44 | --- 45 | 46 | ## Architecture & Design 47 | 48 | ### System Overview 49 | 50 | - **Frontend:** 51 | HTML, CSS, JavaScript (with Bootstrap) for a responsive and clean user interface. 52 | 53 | - **Backend:** 54 | PHP scripts that handle business logic and routing, using PDO for database interactions. 55 | 56 | - **Database Layer:** 57 | A MySQL database stores blog posts and (optionally) user and comment data. 58 | 59 | ### Component Breakdown 60 | 61 | - **User Module:** 62 | (Future Enhancement) Manage user authentication and roles. 63 | 64 | - **Blog Module:** 65 | Manages blog posts with operations to create, edit, view, and delete content. 66 | 67 | - **Admin Dashboard:** 68 | (Optional) Interface for site administrators to manage posts and view statistics. 69 | 70 | --- 71 | 72 | ## Tech Stack 73 | 74 | - **Programming Language:** PHP (7.4+) 75 | - **Database:** MySQL or MariaDB 76 | - **Web Server:** Apache or Nginx (or PHP’s built-in server for development) 77 | - **Frontend:** HTML5, CSS3, JavaScript, Bootstrap 4 78 | - **ORM/Database Interaction:** PDO 79 | - **Dependency Management:** None required for core functionality (Composer optional for further extensions) 80 | 81 | --- 82 | 83 | ## Installation & Setup 84 | 85 | ### Prerequisites 86 | 87 | - PHP 7.4 or above installed. 88 | - MySQL or MariaDB database server. 89 | - Web server (Apache, Nginx, or use PHP built-in server). 90 | - Git (optional, for cloning the repository). 91 | 92 | ### Local Setup 93 | 94 | 1. **Clone the Repository** 95 | 96 | ```bash 97 | git clone https://github.com/your-username/php-blog-cms.git 98 | cd php-blog-cms 99 | ``` 100 | 101 | 2. **Configure Environment** 102 | 103 | Create a file named `config.php` in the project root with your database and application settings. Example: 104 | 105 | ```php 106 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 118 | } catch (PDOException $e) { 119 | die("Could not connect to the database " . DB_NAME . ": " . $e->getMessage()); 120 | } 121 | ?> 122 | ``` 123 | 124 | 3. **Set Up the Database** 125 | 126 | - Create a new database called `blog_cms` (or update `DB_NAME` accordingly): 127 | 128 | ```sql 129 | CREATE DATABASE blog_cms CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci; 130 | ``` 131 | 132 | - Import the provided schema located in `database/schema.sql`: 133 | 134 | ```bash 135 | mysql -u your_db_username -p blog_cms < database/schema.sql 136 | ``` 137 | 138 | --- 139 | 140 | ## Database Schema 141 | 142 | The SQL schema in `database/schema.sql` sets up the following tables: 143 | 144 | - **users** (optional for future authentication): 145 | 146 | ```sql 147 | CREATE TABLE IF NOT EXISTS users ( 148 | id INT AUTO_INCREMENT PRIMARY KEY, 149 | username VARCHAR(50) NOT NULL UNIQUE, 150 | password VARCHAR(255) NOT NULL, 151 | email VARCHAR(100), 152 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 153 | ); 154 | ``` 155 | 156 | - **posts** (for blog posts): 157 | 158 | ```sql 159 | CREATE TABLE IF NOT EXISTS posts ( 160 | id INT AUTO_INCREMENT PRIMARY KEY, 161 | title VARCHAR(150) NOT NULL, 162 | content TEXT NOT NULL, 163 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 164 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 165 | ); 166 | ``` 167 | 168 | - **comments** (optional for future use): 169 | 170 | ```sql 171 | CREATE TABLE IF NOT EXISTS comments ( 172 | id INT AUTO_INCREMENT PRIMARY KEY, 173 | post_id INT NOT NULL, 174 | author VARCHAR(100), 175 | content TEXT NOT NULL, 176 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 177 | FOREIGN KEY (post_id) REFERENCES posts(id) 178 | ); 179 | ``` 180 | 181 | --- 182 | 183 | ## File Structure 184 | 185 | ``` 186 | php-blog-cms/ 187 | ├── config.php 188 | ├── index.php 189 | ├── create_post.php 190 | ├── view_post.php 191 | ├── edit_post.php 192 | ├── delete_post.php 193 | ├── inc/ 194 | │ ├── header.php 195 | │ └── footer.php 196 | └── database/ 197 | └── schema.sql 198 | ``` 199 | 200 | - **config.php:** Contains configuration settings and creates a PDO connection. 201 | - **index.php:** Lists all blog posts. 202 | - **create_post.php:** Provides a form to create new blog posts. 203 | - **view_post.php:** Displays a single post. 204 | - **edit_post.php:** Provides a form to edit an existing post. 205 | - **delete_post.php:** Deletes a post. 206 | - **inc/header.php & inc/footer.php:** Common header and footer for the pages. 207 | - **database/schema.sql:** SQL script to set up the database schema. 208 | 209 | --- 210 | 211 | ## Usage 212 | 213 | ### Web Interface 214 | 215 | - **Homepage:** 216 | Visit `http://localhost/blog-cms/` to see a list of blog posts. 217 | 218 | - **Create Post:** 219 | Navigate to `http://localhost/blog-cms/create_post.php` to create a new post. 220 | 221 | - **View Post:** 222 | Click on "Read More" from any post card on the homepage to view full content. 223 | 224 | - **Edit/Delete Post:** 225 | Use the corresponding buttons on each post to edit or delete posts. 226 | 227 | ### Running the Application with PHP’s Built-In Server 228 | 229 | From the project directory, run: 230 | 231 | ```bash 232 | php -S localhost:8000 233 | ``` 234 | 235 | Then, open your browser at [http://localhost:8000](http://localhost:8000). 236 | 237 | --- 238 | 239 | ## Testing 240 | 241 | ### Manual Testing 242 | 243 | - Use your browser to navigate through the application. 244 | - Verify that posts can be created, viewed, updated, and deleted. 245 | - Ensure that the database updates accordingly. 246 | 247 | ### Automated Testing 248 | 249 | - (Optional) Write PHPUnit tests to cover key functionalities. 250 | - Use Postman to test API endpoints if you extend the application with a REST API. 251 | 252 | --- 253 | 254 | ## Deployment 255 | 256 | ### Apache/Nginx Setup 257 | 258 | - **Apache:** 259 | Configure a virtual host and ensure `mod_rewrite` is enabled for clean URLs. 260 | 261 | - **Nginx:** 262 | Set up a server block similar to the following: 263 | 264 | ```nginx 265 | server { 266 | listen 80; 267 | server_name your-domain.com; 268 | root /path/to/php-blog-cms; 269 | index index.php index.html; 270 | 271 | location / { 272 | try_files \$uri \$uri/ /index.php?\$query_string; 273 | } 274 | 275 | location ~ \.php$ { 276 | include snippets/fastcgi-php.conf; 277 | fastcgi_pass unix:/var/run/php/php7.4-fpm.sock; 278 | } 279 | } 280 | ``` 281 | 282 | ### Docker Deployment 283 | 284 | A sample `Dockerfile` and `docker-compose.yml` are provided (see documentation below for details): 285 | 286 | - **Dockerfile:** 287 | 288 | ```dockerfile 289 | FROM php:7.4-apache 290 | RUN a2enmod rewrite 291 | COPY . /var/www/html/ 292 | RUN docker-php-ext-install pdo pdo_mysql 293 | EXPOSE 80 294 | CMD ["apache2-foreground"] 295 | ``` 296 | 297 | - **docker-compose.yml:** 298 | 299 | ```yaml 300 | version: '3.7' 301 | services: 302 | web: 303 | build: . 304 | ports: 305 | - "80:80" 306 | volumes: 307 | - .:/var/www/html 308 | depends_on: 309 | - db 310 | db: 311 | image: mysql:5.7 312 | restart: always 313 | environment: 314 | MYSQL_ROOT_PASSWORD: your_root_password 315 | MYSQL_DATABASE: blog_cms 316 | MYSQL_USER: your_db_username 317 | MYSQL_PASSWORD: your_db_password 318 | volumes: 319 | - db_data:/var/lib/mysql 320 | volumes: 321 | db_data: 322 | ``` 323 | 324 | --- 325 | 326 | ## Security Considerations 327 | 328 | - **Input Sanitization:** 329 | Use functions like `htmlspecialchars()` and prepared statements via PDO. 330 | - **Password Hashing:** 331 | (Future Enhancement) Use `password_hash()` for storing user passwords securely. 332 | - **Session Security:** 333 | Ensure proper session management and consider HTTPS for production. 334 | - **Error Handling:** 335 | Log errors instead of displaying them in a production environment. 336 | 337 | --- 338 | 339 | ## Future Enhancements 340 | 341 | - **User Authentication & Roles:** 342 | Implement login functionality to restrict access to create/edit/delete operations. 343 | - **Rich Text Editing:** 344 | Integrate a WYSIWYG editor for creating posts. 345 | - **Comments Module:** 346 | Allow readers to leave comments on posts. 347 | - **SEO & Analytics:** 348 | Enhance post URLs, metadata, and add tracking for page views. 349 | - **RESTful API:** 350 | Develop API endpoints for integrating with external applications or a modern JavaScript frontend. 351 | 352 | --- 353 | 354 | ## Troubleshooting & FAQ 355 | 356 | - **Database Connection Issues:** 357 | Ensure that `config.php` has the correct database credentials and that the MySQL server is running. 358 | - **404 Errors:** 359 | Verify the file paths and URL rewriting configuration in your web server. 360 | - **Permission Issues:** 361 | Ensure your web server has the necessary permissions to read the project files. 362 | 363 | --- 364 | 365 | ## Contributing 366 | 367 | Contributions are welcome! Follow these steps: 368 | 1. Fork the repository. 369 | 2. Create a feature branch: `git checkout -b feature/your-feature`. 370 | 3. Commit your changes with clear messages. 371 | 4. Push your branch and open a pull request detailing your changes. 372 | 373 | Please adhere to the coding standards and include tests where applicable. 374 | 375 | --- 376 | 377 | ## License 378 | 379 | This project is licensed under the [MIT License](LICENSE). 380 | 381 | --- 382 | 383 | ## Authors 384 | 385 | The UNC-CH Google Developer Student Club (GDSC) team. 386 | -------------------------------------------------------------------------------- /config.php: -------------------------------------------------------------------------------- 1 | setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 20 | } catch (PDOException $e) { 21 | die("Could not connect to the database " . DB_NAME . ": " . $e->getMessage()); 22 | } 23 | ?> 24 | -------------------------------------------------------------------------------- /create_post.php: -------------------------------------------------------------------------------- 1 | prepare("INSERT INTO posts (title, content) VALUES (?, ?)"); 13 | \$stmt->execute([\$title, \$content]); 14 | 15 | // Redirect to home page after creation 16 | header("Location: index.php"); 17 | exit; 18 | } else { 19 | \$error = "Both title and content are required."; 20 | } 21 | } 22 | 23 | require_once 'inc/header.php'; 24 | ?> 25 | 26 |

Create New Post

27 | 28 |
29 | 30 |
31 |
32 | 33 | 34 |
35 |
36 | 37 | 38 |
39 | 40 |
41 | 42 | 43 | -------------------------------------------------------------------------------- /database/schema.sql: -------------------------------------------------------------------------------- 1 | -- schema.sql 2 | 3 | -- Create users table (for future authentication, optional) 4 | CREATE TABLE IF NOT EXISTS users ( 5 | id INT AUTO_INCREMENT PRIMARY KEY, 6 | username VARCHAR(50) NOT NULL UNIQUE, 7 | password VARCHAR(255) NOT NULL, 8 | email VARCHAR(100), 9 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 10 | ); 11 | 12 | -- Create posts table 13 | CREATE TABLE IF NOT EXISTS posts ( 14 | id INT AUTO_INCREMENT PRIMARY KEY, 15 | title VARCHAR(150) NOT NULL, 16 | content TEXT NOT NULL, 17 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 18 | updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP 19 | ); 20 | 21 | -- (Optional) Create comments table for future use 22 | CREATE TABLE IF NOT EXISTS comments ( 23 | id INT AUTO_INCREMENT PRIMARY KEY, 24 | post_id INT NOT NULL, 25 | author VARCHAR(100), 26 | content TEXT NOT NULL, 27 | created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, 28 | FOREIGN KEY (post_id) REFERENCES posts(id) 29 | ); 30 | -------------------------------------------------------------------------------- /delete_post.php: -------------------------------------------------------------------------------- 1 | prepare("DELETE FROM posts WHERE id = ?"); 13 | \$stmt->execute([\$id]); 14 | 15 | header("Location: index.php"); 16 | exit; 17 | ?> 18 | -------------------------------------------------------------------------------- /edit_post.php: -------------------------------------------------------------------------------- 1 | prepare("SELECT * FROM posts WHERE id = ?"); 13 | \$stmt->execute([\$id]); 14 | \$post = \$stmt->fetch(PDO::FETCH_ASSOC); 15 | 16 | if (!\$post) { 17 | die("Post not found."); 18 | } 19 | 20 | if (\$_SERVER['REQUEST_METHOD'] == 'POST') { 21 | // Get form inputs 22 | \$title = \$_POST['title'] ?? ''; 23 | \$content = \$_POST['content'] ?? ''; 24 | 25 | if (!empty(\$title) && !empty(\$content)) { 26 | // Update post in database 27 | \$stmt = \$pdo->prepare("UPDATE posts SET title = ?, content = ?, updated_at = NOW() WHERE id = ?"); 28 | \$stmt->execute([\$title, \$content, \$id]); 29 | 30 | header("Location: view_post.php?id=" . \$id); 31 | exit; 32 | } else { 33 | \$error = "Both title and content are required."; 34 | } 35 | } 36 | 37 | require_once 'inc/header.php'; 38 | ?> 39 | 40 |

Edit Post

41 | 42 |
43 | 44 |
45 |
46 | 47 | 48 |
49 |
50 | 51 | 52 |
53 | 54 | Cancel 55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /inc/footer.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /inc/header.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PHP Blog CMS 6 | 7 | 8 | 9 | 22 |
23 | -------------------------------------------------------------------------------- /index.php: -------------------------------------------------------------------------------- 1 | query("SELECT * FROM posts ORDER BY created_at DESC"); 8 | \$posts = \$stmt->fetchAll(PDO::FETCH_ASSOC); 9 | ?> 10 | 11 |

Blog Posts

12 | 0): ?> 13 | 14 |
15 |
16 |

17 |

18 | 19 |

20 | Read More 21 | Edit 22 | Delete 23 |

Posted on

24 |
25 |
26 | 27 | 28 |

No posts found. Create one now.

29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /view_post.php: -------------------------------------------------------------------------------- 1 | prepare("SELECT * FROM posts WHERE id = ?"); 13 | \$stmt->execute([\$id]); 14 | \$post = \$stmt->fetch(PDO::FETCH_ASSOC); 15 | 16 | if (!\$post) { 17 | die("Post not found."); 18 | } 19 | 20 | require_once 'inc/header.php'; 21 | ?> 22 | 23 |

24 |

Posted on

25 |
26 | 27 |
28 |

29 | Edit 30 | Delete 31 |

32 | Back to Posts 33 | 34 | 35 | --------------------------------------------------------------------------------