diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000..c28aea0 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: ['Nephiaust'] diff --git a/.github/workflows/psalm.yml b/.github/workflows/psalm.yml new file mode 100644 index 0000000..931deb7 --- /dev/null +++ b/.github/workflows/psalm.yml @@ -0,0 +1,35 @@ +name: Psalm Static analysis + +on: [push, pull_request] + +jobs: + psalm: + name: Psalm + permissions: + actions: read + checks: read + contents: read + deployments: none + id-token: none + issues: write + discussions: read + packages: read + pages: none + pull-requests: write + repository-projects: read + security-events: write + statuses: write + runs-on: ubuntu-latest + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Psalm + uses: docker://ghcr.io/psalm/psalm-github-actions:5.7.7 + with: + security_analysis: true + report_file: results.sarif + - name: Upload Security Analysis results to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 485dee6..8ea58cf 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,44 @@ .idea +/uploads/* + +# General Apple files +.DS_Store +.AppleDouble +.LSOverride + +# Apple Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Built Visual Studio Code Extensions +*.vsix diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..6c2ff60 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "master" + ] +} \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ea64188 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +//TODO \ No newline at end of file diff --git a/README.md b/README.md index 758e35a..4e4c566 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,32 @@ # Simple-PHP-Blog -Simple blog system for personal development using procedural PHP and MYSQL. +Simple blog system for personal development using procedural PHP and MySQLi. It allows you to create, edit, delete posts to get you started on your journey. If you are building your own from scratch this will give the head start that you need. For educational purposes only. -# Setup +**__Security is not guaranteed with this system, best efforts have been made to make it secure__** -Update the `connect.php` file with your database credentials. -Import the `database.sql` file. +Setup +=== -If installed on a sub-folder, edit the `config.php` and replace the empty constant with the folder's name. - -The pagination results per page can be set on the `config.php` file. +1. Create a MySQL database on your MySQL server, take note of the details (username, password, database name, server name) +2. Import the `database.sql` file into the new database you created +3. Edit the `config.php` file + 1. Edit the MySQL details to match your SQL server login details (e.g. server name, username, password, database) + 2. Edit the `SITE_ROOT` if you are putting it in a folder/sub-directory (e.g. www.example.com/myblog/, you would enter 'myblog' there) + 3. _OPTIONAL_ Change the number of blog posts to show per page with the `PAGINATION` option + 4. _OPTIONAL_ Set the `DEBUG_MODE` option to `true` if you want/need to see any and all errors +4. Upload all the files to your web server +5. Go to your new site (e.g. www.example.com/myblog/) ### URL Rewrite -The latest update introduces 'slugs', also known as 'SEO URLs'. -After you update to the latest version, click on the "Generate slugs (SEO URLs)" button on the admin dashboard and slugs will be generated for all existing posts. +The system now uses **slugs**, also known as **SEO URLs** -The blog posts URL structure is like this: `http://localhost/p/4/apple-reveals-apple-watch-series-7` +The blog posts URL structure is like this: `http://www.example.com/myblog/p/4/apple-reveals-apple-watch-series-7`, where the `p/4/apple-reveals-apple-watch-series-7` is the slug -If you use Apache, enable the Apache rewrite module for the .htaccess rewrite rule to work. +#### Apache servers +There is an .htaccess file that has the required rewrite module and rule in the files. +#### Nginx servers If you use NGINX, you can insert something similar to the code below in your NGINX configuration block. ``` location / { @@ -27,14 +34,19 @@ location / { } ``` -# Default Admin Login +Using the Simple-PHP-Blog +=== + +The system is quite easy to use, as there isnt much work required to do a simple blog. + +## Default Admin Login Username: admin Password: 12345 -There is no way to update the admin password through the dashboard yet. -To change your password, hash your password with PHP's `password_hash()` function. Then update the database value with the new password hash. +**__There is no way to update the admin password through the dashboard yet.__** +**__To change your password, hash your password with PHP's `password_hash()` function. Then update the database value with the new password hash.__** -# Screenshots +## Screenshots ![screenshot_01](https://user-images.githubusercontent.com/16838612/66112823-78d32e00-e5c3-11e9-9b38-93ba488071e0.jpg) ![screenshot_02](https://user-images.githubusercontent.com/16838612/66112874-8d172b00-e5c3-11e9-97e4-590da5675100.jpg) diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..61335f7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,18 @@ +# Security Policy + +## Supported Versions + +Use this section to tell people about which versions of your project are +currently being supported with security updates. + +| Version | Supported | +| ------- | ------------------ | +| 4.x.x | :white_check_mark: | + +## Reporting a Vulnerability + +Use this section to tell people how to report a vulnerability. + +Tell them where to go, how often they can expect to get an update on a +reported vulnerability, what to expect if the vulnerability is accepted or +declined, etc. diff --git a/_config.yml b/_config.yml deleted file mode 100644 index c419263..0000000 --- a/_config.yml +++ /dev/null @@ -1 +0,0 @@ -theme: jekyll-theme-cayman \ No newline at end of file diff --git a/admin.php b/admin.php index 6a0163e..5d45f66 100644 --- a/admin.php +++ b/admin.php @@ -1,16 +1,21 @@ -

Admin Dashboard

-
-

Welcome ,

-

Create new post

-

Generate slugs (SEO URLs)

+

Admin Dashboard

+
+

Welcome ,

+

Create new post

-
-
Posts
+
+
Posts
$totalpages) { $page = $totalpages; @@ -54,19 +59,19 @@ $author = $row['posted_by']; $time = $row['date']; - $permalink = "p/".$id ."/".$slug; - ?> + $permalink = "p/" . $id . "/" . $slug; +?> - Edit | Delete + view post

"; ?> + Edit | Delete - "; diff --git a/cat.php b/cat.php index 7c47b62..96c4017 100644 --- a/cat.php +++ b/cat.php @@ -1,8 +1,13 @@ '; } -include("footer.php"); \ No newline at end of file +include("footer.php"); diff --git a/categories.php b/categories.php index b3b8a58..d4b158d 100644 --- a/categories.php +++ b/categories.php @@ -1,22 +1,24 @@ -

Categories

+
+

Categories +

"; while ($row = mysqli_fetch_assoc($result)) { $id = $row['id']; $catname = $row['catname']; $description = $row['description']; - ?> +?>

- "; -}else{ +} else { echo "
No Category found.
"; } diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..10ea3b1 --- /dev/null +++ b/composer.json @@ -0,0 +1,42 @@ +{ + "name": "composer/composer", + "type": "library", + "description": "Composer helps you declare, manage and install dependencies of PHP projects. It ensures you have the right stack everywhere.", + "keywords": [ + ], + "authors": [ + { + "name": "Philipinho (Philip)", + "homepage": "https://github.com/Philipinho", + "role": "Developer" + }, + { + "name": "7s9n (Hussein Sarea)", + "homepage": "https://github.com/7s9n", + "role": "Contributor" + }, + { + "name": "ankheur (Pierrick Rancoeur)", + "homepage": "https://github.com/ankheur", + "role": "Contributor" + }, + { + "name": "terzinnorbert", + "homepage": "https://github.com/terzinnorbert", + "role": "Contributor" + }, + { + "name": "myckgoncalves (Myck Gonçalves)", + "homepage": "https://github.com/myckgoncalves", + "role": "Contributor" + }, + { + "name": "rastating", + "homepage": "https://github.com/rastating", + "role": "Contributor" + } + ], + "require": { + "php": "^7.2.5 || ^8.0" + } +} diff --git a/config.php b/config.php index 330585f..6fba2fa 100644 --- a/config.php +++ b/config.php @@ -1,3 +1,13 @@ "; +set_time_limit(0); +require_once 'functions/connect.php'; +require_once 'functions/functions.php'; +require_once 'functions/security.php'; + +# Turn on debug mode, and show all errors. +if (DEBUG_MODE == true) { + error_reporting(E_ALL); + ini_set("display_errors", 1); } + $sql = "SELECT * FROM posts WHERE slug IS NULL"; $result = mysqli_query($dbcon, $sql); @@ -26,21 +28,19 @@ $description = $row['description']; $slug = $row['slug']; - if (is_null($slug)){ + if (is_null($slug)) { $new_slug = slug($title); $sql2 = "UPDATE posts SET slug = '$new_slug' WHERE id = $id"; if (mysqli_query($dbcon, $sql2)) { - $permalink = "p/".$id."/".$new_slug; + $permalink = "p/" . $id . "/" . $new_slug; - echo "Slug successfully generated for $title
" ; + echo "Slug successfully generated for $title
"; } else { echo "Failed to generate slug for post ID: $id." . mysqli_connect_error(); } - } - } -mysqli_close($dbcon); \ No newline at end of file +mysqli_close($dbcon); diff --git a/edit.php b/edit.php index f013b86..5cdd594 100644 --- a/edit.php +++ b/edit.php @@ -1,9 +1,14 @@ -
+

Edit Post -

-

Goto post

+

Goto post

- +

@@ -64,12 +70,12 @@

+ Delete Post +

-
+

- + diff --git a/functions.php b/functions.php deleted file mode 100644 index f27a0b7..0000000 --- a/functions.php +++ /dev/null @@ -1,17 +0,0 @@ - diff --git a/functions/security.php b/functions/security.php new file mode 100644 index 0000000..70c690b --- /dev/null +++ b/functions/security.php @@ -0,0 +1,11 @@ + + - + PHP Blog - + + -
-

PHP Blog

-
- -
- Home - New Post"; - echo "Admin Panel"; - echo "Logout"; - } else { - echo "Login"; - } - ?> -
- -
-
-

- -

-

- -

-
-
\ No newline at end of file +
+

PHP Blog

+
+ +
+ Home + New Post"; + echo "Admin Panel"; + echo "Logout"; + } else { + echo "Login"; + } + ?> +
+ +
+
+

+ +

+

+ +

+
+
\ No newline at end of file diff --git a/index.php b/index.php index 03fc66c..9eee976 100644 --- a/index.php +++ b/index.php @@ -1,6 +1,12 @@
@@ -19,7 +25,7 @@ $page = 1; if (isset($_GET['page']) && is_numeric($_GET['page'])) { - $page = (INT)$_GET['page']; + $page = (int)$_GET['page']; } if ($page > $totalpages) { @@ -37,56 +43,56 @@ if (mysqli_num_rows($result) < 1) { echo '
No post yet!
'; } else { - while ($row = mysqli_fetch_assoc($result)) { + while ($row = mysqli_fetch_assoc($result)) { - $id = htmlentities($row['id']); - $title = htmlentities($row['title']); - $des = htmlentities(strip_tags($row['description'])); - $slug = htmlentities($row['slug']); - $time = htmlentities($row['date']); + $id = htmlentities($row['id']); + $title = htmlentities($row['title']); + $des = htmlentities(strip_tags($row['description'])); + $slug = htmlentities($row['slug']); + $time = htmlentities($row['date']); - $permalink = "p/".$id ."/".$slug; + $permalink = "p/" . $id . "/" . $slug; - echo '
'; - echo "

$title

"; + echo '

'; + echo "

$title

"; - echo substr($des, 0, 100); + echo substr($des, 0, 100); - echo '

'; - echo "Read more...

"; + echo '
'; + echo "Read more...

"; - echo '
'; - echo "
$time
"; - echo '
'; -} + echo '
'; + echo "
$time
"; + echo '
'; + } -echo "

"; + echo "

"; -if ($page > 1) { - echo "«"; - $prevpage = $page - 1; - echo "<"; -} + if ($page > 1) { + echo "«"; + $prevpage = $page - 1; + echo "<"; + } -$range = 5; -for ($x = $page - $range; $x < ($page + $range) + 1; $x++) { - if (($x > 0) && ($x <= $totalpages)) { - if ($x == $page) { - echo "
$x
"; - } else { - echo "$x"; + $range = 5; + for ($x = $page - $range; $x < ($page + $range) + 1; $x++) { + if (($x > 0) && ($x <= $totalpages)) { + if ($x == $page) { + echo "
$x
"; + } else { + echo "$x"; + } } } -} -if ($page != $totalpages) { - $nextpage = $page + 1; - echo ">"; - echo "»"; -} + if ($page != $totalpages) { + $nextpage = $page + 1; + echo ">"; + echo "»"; + } -echo "

"; + echo "

"; } include("categories.php"); diff --git a/login.php b/login.php index 09265c4..8fed782 100644 --- a/login.php +++ b/login.php @@ -1,37 +1,72 @@ Login'; -if (isset($_POST['log'])) { - $username = mysqli_real_escape_string($dbcon, $_POST['username']); - $password = mysqli_real_escape_string($dbcon, $_POST['password']); +if (isset($_POST['username'])) {$CurrentUser = htmlentities(strip_tags($_POST['username']), ENT_SUBSTITUTE);} - $sql = "SELECT * FROM admin WHERE username = '$username'"; +// Lets check to see if the call was a HTTP POST request +// If it is, display the admin page +// If it is NOT, display the login page +if ($_SERVER["REQUEST_METHOD"] == "POST") { + // Now to check if the username field is not empty, otherwise throw an error. + if (empty(trim($_POST["username"]))) { + echo "
Username or password not supplied.
"; + } else { + // We have data for a username, now lets save it in a SQL safe string (e.g. automatically add escape characters, etc.) + $username = mysqli_real_escape_string($dbcon, $_POST['username']); + } + + // Do the same for the password field. + if (empty(trim($_POST["password"]))) { + echo "
Username or password not supplied!
"; + } else { + // And again save the password in a SQL safe string + $password = mysqli_real_escape_string($dbcon, $_POST['password']); + } + + // Build the SQL statement to get the user details (so we can then verify the user exists AND that the password is valid) + $sql = "SELECT `id`, `username`, `password`, `displayname` FROM users WHERE username = '$username'"; + + // Request the data from the SQL server, process it AND count the number of rows. $result = mysqli_query($dbcon, $sql); $row = mysqli_fetch_assoc($result); $row_count = mysqli_num_rows($result); - + // Check that the user only exists once in the SQL database AND that the password is matching. if ($row_count == 1 && password_verify($password, $row['password'])) { + // This part we store some information in the PHP session information, so we can use it as a later time (e.g. the user ID) + $_SESSION['displayname'] = $row['displayname']; + $_SESSION['userid'] = $row['id']; $_SESSION['username'] = $username; + $_SESSION["loggedin"] = true; + + // Now we redirect the user to the admin portal. header("location: admin.php"); } else { echo "
Incorrect username or password.
"; } } - ?> +?> -
- - - - -

-
+
+ + + + +

+
- ", - $permalink); + $permalink = "p/" . mysqli_insert_id($dbcon) . "/" . $slug; + printf( + "Posted successfully. ", + $permalink + ); } else { - ?> +?>
@@ -35,7 +41,7 @@

- +

@@ -44,7 +50,7 @@

- Showing results for $q
"; + $sql = "SELECT * FROM posts WHERE title LIKE '%{$q}%' OR description LIKE '%{$q}%'"; + $result = mysqli_query($dbcon, $sql); - while ($row = mysqli_fetch_assoc($result)) { + if (mysqli_num_rows($result) < 1) { + echo "Nothing found."; + } else { - $id = htmlentities($row['id']); - $title = htmlentities($row['title']); - $des = htmlentities(strip_tags($row['description'])); - $slug = htmlentities(strip_tags($row['slug'])); - $time = htmlentities($row['date']); + echo "
Showing results for $q
"; - $permalink = "p/".$id ."/".$slug; + while ($row = mysqli_fetch_assoc($result)) { - echo '
'; - echo "

$title

"; + $id = htmlentities($row['id']); + $title = htmlentities($row['title']); + $des = htmlentities(strip_tags($row['description'])); + $slug = htmlentities(strip_tags($row['slug'])); + $time = htmlentities($row['date']); - echo substr($des, 0, 100); + $permalink = "p/" . $id . "/" . $slug; - echo '

'; - echo "Read more..."; + echo '
'; + echo "

$title

"; - echo '

'; - echo "$time
"; - echo '
'; + echo substr($des, 0, 100); - } + echo '

'; + echo "Read more..."; + echo '
'; + echo "$time
"; + echo '
'; } + } } include("footer.php"); diff --git a/security.php b/security.php deleted file mode 100644 index 33df578..0000000 --- a/security.php +++ /dev/null @@ -1,5 +0,0 @@ -'; +echo '
'; echo "

$title

"; echo '
'; @@ -37,12 +42,12 @@ - +?> + - del.php?id=" onclick="return confirm('Are you sure you want to delete this post?'); ">[Delete] +
+
';