├── .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 |
5 |
6 |
7 |
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 |
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 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/inc/footer.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |