Project 1 program with output 15 Q/A 5 MCQs

Project Objectives – by building this project you will:

  • Design the database of a CMS with users, articles and comments.
  • Build registration, login, logout and role-based access with sessions and password_hash().
  • Let authors compose articles that stay pending until a moderator publishes them.
  • Show published articles on the home page and let registered users comment.
  • Use MySQLi object-oriented code with prepared statements throughout.
Synopsis

This project turns the ideas of Chapter 40 into a working application. The CMS allows registered users to post articles. Those articles are labelled pending until a user with the proper permission – a moderator or administrator – publishes them. Once published, an article appears as the newest article on the home page. Unregistered visitors can read articles but cannot post. Registered users can also post comments; when a visitor views a full article, all its comments are displayed below it. An administrator can change the access level of every user.

FeatureImplemented in
Registration and loginregister.php, login.php, logout.php (sessions, password_hash(), password_verify())
Shared codeinc/config.php (connection, helpers), inc/header.php, inc/footer.php
Home pageindex.php – latest published articles
Composecompose.php – new article, saved as pending
Moderationpending.php – list of pending articles; review.php – edit, publish or delete
Reading and commentsarticle.php – full article, comments and comment form
Administrationadmin.php – users and their access levels

The pages are stored in htdocs/phpbook/p1. Every database statement that contains outside data is a prepared statement (Chapter 36).

Step 1 – The Database
SQL SESSION SQL typed in the MariaDB monitor (XAMPP Shell → mysql -u root) or the phpMyAdmin SQL tab
MariaDB [(none)]> DROP DATABASE IF EXISTS cms;
Query OK, 3 rows affected

MariaDB [(none)]> CREATE DATABASE cms CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
Query OK, 1 row affected

MariaDB [(none)]> USE cms;
Database changed

MariaDB [cms]> CREATE TABLE cms_users (
  user_id      INT AUTO_INCREMENT PRIMARY KEY,
  name         VARCHAR(50)  NOT NULL,
  email        VARCHAR(100) NOT NULL UNIQUE,
  password     VARCHAR(255) NOT NULL,
  access_level TINYINT NOT NULL DEFAULT 1,
  created_on   DATETIME DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB;
Query OK, 0 rows affected

MariaDB [cms]> CREATE TABLE cms_articles (
  article_id   INT AUTO_INCREMENT PRIMARY KEY,
  user_id      INT NOT NULL,
  is_published TINYINT(1) NOT NULL DEFAULT 0,
  submit_date  DATETIME NOT NULL,
  publish_date DATETIME NULL,
  title        VARCHAR(150) NOT NULL,
  body         TEXT NOT NULL,
  FOREIGN KEY (user_id) REFERENCES cms_users(user_id)
) ENGINE=InnoDB;
Query OK, 0 rows affected

MariaDB [cms]> CREATE TABLE cms_comments (
  comment_id   INT AUTO_INCREMENT PRIMARY KEY,
  article_id   INT NOT NULL,
  user_id      INT NOT NULL,
  comment_date DATETIME NOT NULL,
  comment      TEXT NOT NULL,
  FOREIGN KEY (article_id) REFERENCES cms_articles(article_id) ON DELETE CASCADE,
  FOREIGN KEY (user_id) REFERENCES cms_users(user_id)
) ENGINE=InnoDB;
Query OK, 0 rows affected

MariaDB [cms]> SHOW TABLES;
+---------------+
| Tables_in_cms |
+---------------+
| cms_articles  |
| cms_comments  |
| cms_users     |
+---------------+
3 rows in set

access_level stores the role: 1 = user (author), 2 = moderator, 3 = administrator. ON DELETE CASCADE deletes the comments of an article automatically when the article is deleted.

Step 2 – Shared Configuration and Helpers
inc/config.php
<?php
session_start();
date_default_timezone_set("Asia/Kolkata");

$db = new mysqli("localhost", "root", "", "cms");
$db->set_charset("utf8mb4");

const LEVELS = [1 => "User", 2 => "Moderator", 3 => "Administrator"];

function e(?string $s): string                       // escape for HTML output
{
    return htmlspecialchars($s ?? "", ENT_QUOTES, "UTF-8");
}
function user(): ?array                              // the logged-in user or null
{
    return $_SESSION["user"] ?? null;
}
function require_level(int $level): void            // protect a page
{
    if (!user() || user()["access_level"] < $level) {
        header("Location: login.php");
        exit;
    }
}
function flash(?string $msg = null): ?string         // one-time message
{
    if ($msg !== null) { $_SESSION["flash"] = $msg; return null; }
    $m = $_SESSION["flash"] ?? null;
    unset($_SESSION["flash"]);
    return $m;
}
inc/header.php
<!DOCTYPE html>
<html><head><meta charset="utf-8"><title><?= e($title ?? "CMS") ?></title>
<style>
 body{font-family:Arial;font-size:13px;margin:0;width:600px}
 .top{background:#173b6c;color:#fff;padding:8px} .top a{color:#ffd483;margin-right:8px}
 .main{padding:8px} .flash{background:#e7f6ec;border:1px solid #9c9;padding:4px}
 .art{border-bottom:1px solid #ccc;padding:4px 0} small{color:#666}
</style></head>
<body>
<div class="top"><b>PHP CMS</b> &nbsp;
  <a href="index.php">Home</a>
  <?php if (user()): ?>
    <a href="compose.php">Compose</a>
    <?php if (user()["access_level"] >= 2): ?><a href="pending.php">Pending</a><?php endif; ?>
    <?php if (user()["access_level"] >= 3): ?><a href="admin.php">Admin</a><?php endif; ?>
    <a href="logout.php">Logout</a>
    <small style="color:#fff">(<?= e(user()["name"]) ?>, <?= LEVELS[user()["access_level"]] ?>)</small>
  <?php else: ?>
    <a href="login.php">Login</a> <a href="register.php">Register</a>
  <?php endif; ?>
</div>
<div class="main">
<?php if ($m = flash()): ?><p class="flash"><?= e($m) ?></p><?php endif; ?>
inc/footer.php
</div>
<div style="border-top:1px solid #ccc;padding:4px 8px"><small>&copy; <?= date("Y") ?> PHP CMS</small></div>
</body></html>
Step 3 – Creating the Administrator

The first account must be an administrator. setup.php is run once and then deleted:

setup.php – run once
<?php
require "inc/config.php";
$hash = password_hash("Admin@123", PASSWORD_DEFAULT);
$db->execute_query(
    "INSERT INTO cms_users (name, email, password, access_level) VALUES (?, ?, ?, 3)",
    ["Site Admin", "admin@cms.test", $hash]);
echo "Administrator created with id " . $db->insert_id . ". Delete setup.php now!";
OUTPUT browser · http://localhost/phpbook/p1/setup.php
Administrator created with id 1. Delete setup.php now!
Step 4 – Registration, Login and Logout
register.php
<?php
require "inc/config.php";
$errors = [];
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name  = trim($_POST["name"] ?? "");
    $email = trim($_POST["email"] ?? "");
    $pass  = $_POST["password"] ?? "";
    if (strlen($name) < 3)                          $errors[] = "Name must have at least 3 characters.";
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) $errors[] = "Invalid e-mail address.";
    if (strlen($pass) < 8)                          $errors[] = "Password must have at least 8 characters.";
    if (!$errors) {
        try {
            $db->execute_query("INSERT INTO cms_users (name, email, password) VALUES (?, ?, ?)",
                               [$name, $email, password_hash($pass, PASSWORD_DEFAULT)]);
            flash("Registration successful. Please log in.");
            header("Location: login.php");
            exit;
        } catch (mysqli_sql_exception $ex) {
            $errors[] = $ex->getCode() == 1062 ? "This e-mail is already registered." : "Registration failed.";
        }
    }
}
$title = "Register";
require "inc/header.php";
?>
<h3>Register</h3>
<?php foreach ($errors as $er): ?><div style="color:#c00"><?= e($er) ?></div><?php endforeach; ?>
<form method="post">
  Name <input name="name" id="name" value="<?= e($_POST["name"] ?? "") ?>"><br>
  E-mail <input name="email" id="email" value="<?= e($_POST["email"] ?? "") ?>"><br>
  Password <input type="password" name="password" id="password"><br>
  <input type="submit" value="Register" id="go">
</form>
<?php require "inc/footer.php"; ?>
login.php
<?php
require "inc/config.php";
$error = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $row = $db->execute_query("SELECT user_id, name, email, password, access_level
                               FROM cms_users WHERE email = ?", [$_POST["email"] ?? ""])->fetch_assoc();
    if ($row && password_verify($_POST["password"] ?? "", $row["password"])) {
        session_regenerate_id(true);
        unset($row["password"]);                    // never keep the hash in the session
        $_SESSION["user"] = $row;
        flash("Welcome, " . $row["name"] . "!");
        header("Location: index.php");
        exit;
    }
    $error = "Wrong e-mail or password.";
}
$title = "Login";
require "inc/header.php";
?>
<h3>Login</h3>
<div style="color:#c00"><?= e($error) ?></div>
<form method="post">
  E-mail <input name="email" id="email"> Password <input type="password" name="password" id="password">
  <input type="submit" value="Login" id="go">
</form>
<?php require "inc/footer.php"; ?>
logout.php
<?php
require "inc/config.php";
$_SESSION = [];
session_destroy();
session_start();
flash("You have been logged out.");
header("Location: index.php");

A new user registers (with one mistake first):

OUTPUT browser · register.php with an invalid e-mail and a short password
Browser output
OUTPUT browser · after registering and logging in as Asha (redirected to index.php)
Browser output
Step 5 – Home Page
index.php
<?php
require "inc/config.php";
$articles = $db->query(
    "SELECT a.article_id, a.title, a.body, a.publish_date, u.name,
            (SELECT COUNT(*) FROM cms_comments c WHERE c.article_id = a.article_id) AS comments
     FROM cms_articles a JOIN cms_users u ON a.user_id = u.user_id
     WHERE a.is_published = 1
     ORDER BY a.publish_date DESC LIMIT 10");
$title = "Home";
require "inc/header.php";
?>
<h3>Latest articles</h3>
<?php if ($articles->num_rows == 0): ?>
  <p>No articles have been published yet.</p>
<?php endif; ?>
<?php foreach ($articles as $a): ?>
  <div class="art">
    <a href="article.php?id=<?= $a["article_id"] ?>"><b><?= e($a["title"]) ?></b></a><br>
    <small>by <?= e($a["name"]) ?> on <?= date("d M Y, h:i A", strtotime($a["publish_date"])) ?>
           · <?= $a["comments"] ?> comment(s)</small><br>
    <?= e(mb_strimwidth($a["body"], 0, 120, "…")) ?>
  </div>
<?php endforeach; ?>
<?php require "inc/footer.php"; ?>
Step 6 – Composing an Article
compose.php
<?php
require "inc/config.php";
require_level(1);                                    // any logged-in user
$error = "";
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $t = trim($_POST["title"] ?? "");
    $b = trim($_POST["body"] ?? "");
    if ($t === "" || strlen($b) < 20) {
        $error = "Please enter a title and at least 20 characters of text.";
    } else {
        $db->execute_query("INSERT INTO cms_articles (user_id, submit_date, title, body)
                            VALUES (?, NOW(), ?, ?)", [user()["user_id"], $t, $b]);
        flash("Article #" . $db->insert_id . " submitted. It is pending until a moderator publishes it.");
        header("Location: index.php");
        exit;
    }
}
$title = "Compose";
require "inc/header.php";
?>
<h3>Compose an article</h3>
<div style="color:#c00"><?= e($error) ?></div>
<form method="post">
  Title<br><input name="title" id="title" size="50"><br>
  Text<br><textarea name="body" id="body" rows="5" cols="60"></textarea><br>
  <input type="submit" value="Submit for review" id="go">
</form>
<?php require "inc/footer.php"; ?>
OUTPUT browser · after Asha submits an article (the home page still shows no published article)
Browser output
Step 7 – Moderation: Pending Articles and Review
pending.php
<?php
require "inc/config.php";
require_level(2);                                    // moderators and administrators
$rows = $db->query("SELECT a.article_id, a.title, a.submit_date, u.name
                    FROM cms_articles a JOIN cms_users u ON a.user_id = u.user_id
                    WHERE a.is_published = 0 ORDER BY a.submit_date");
$title = "Pending articles";
require "inc/header.php";
?>
<h3>Pending articles (<?= $rows->num_rows ?>)</h3>
<?php foreach ($rows as $r): ?>
  <div class="art"><a href="review.php?id=<?= $r["article_id"] ?>"><?= e($r["title"]) ?></a>
    <small>by <?= e($r["name"]) ?>, submitted <?= date("d M Y H:i", strtotime($r["submit_date"])) ?></small></div>
<?php endforeach; ?>
<?php require "inc/footer.php"; ?>
review.php
<?php
require "inc/config.php";
require_level(2);
$id = (int) ($_GET["id"] ?? $_POST["id"] ?? 0);
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $action = $_POST["action"] ?? "";
    if ($action === "delete") {
        $db->execute_query("DELETE FROM cms_articles WHERE article_id = ?", [$id]);
        flash("Article deleted.");
        header("Location: pending.php");
        exit;
    }
    $db->execute_query("UPDATE cms_articles SET title = ?, body = ? WHERE article_id = ?",
                       [trim($_POST["title"]), trim($_POST["body"]), $id]);
    if ($action === "publish") {
        $db->execute_query("UPDATE cms_articles SET is_published = 1, publish_date = NOW()
                            WHERE article_id = ?", [$id]);
        flash("Article published.");
        header("Location: index.php");
        exit;
    }
    flash("Changes saved.");
}
$a = $db->execute_query("SELECT * FROM cms_articles WHERE article_id = ?", [$id])->fetch_assoc();
if (!$a) { http_response_code(404); exit("Article not found"); }
$title = "Review";
require "inc/header.php";
?>
<h3>Review article #<?= $id ?> <small>(<?= $a["is_published"] ? "published" : "pending" ?>)</small></h3>
<form method="post">
  <input type="hidden" name="id" value="<?= $id ?>">
  <input name="title" id="title" size="50" value="<?= e($a["title"]) ?>"><br>
  <textarea name="body" id="body" rows="5" cols="60"><?= e($a["body"]) ?></textarea><br>
  <button name="action" value="save" id="save">Save</button>
  <button name="action" value="publish" id="publish">Publish</button>
  <button name="action" value="delete" id="delete">Delete</button>
</form>
<?php require "inc/footer.php"; ?>

The administrator logs in and opens the list of pending articles:

OUTPUT browser · pending.php, logged in as Site Admin
Browser output

He corrects the title in the review page and publishes the article:

OUTPUT browser · after clicking Publish in review.php (redirected to the home page)
Browser output
Step 8 – Reading an Article and Commenting
article.php
<?php
require "inc/config.php";
$id = (int) ($_GET["id"] ?? 0);
if ($_SERVER["REQUEST_METHOD"] === "POST" && user()) {
    $c = trim($_POST["comment"] ?? "");
    if ($c !== "") {
        $db->execute_query("INSERT INTO cms_comments (article_id, user_id, comment_date, comment)
                            VALUES (?, ?, NOW(), ?)", [$id, user()["user_id"], $c]);
        flash("Thank you for your comment.");
    }
    header("Location: article.php?id=$id");          // Post/Redirect/Get
    exit;
}
$a = $db->execute_query("SELECT a.*, u.name FROM cms_articles a JOIN cms_users u USING (user_id)
                         WHERE a.article_id = ? AND a.is_published = 1", [$id])->fetch_assoc();
if (!$a) { http_response_code(404); exit("Article not found"); }
$comments = $db->execute_query("SELECT c.comment, c.comment_date, u.name
                                FROM cms_comments c JOIN cms_users u USING (user_id)
                                WHERE c.article_id = ? ORDER BY c.comment_date", [$id]);
$title = $a["title"];
require "inc/header.php";
?>
<h3><?= e($a["title"]) ?></h3>
<small>by <?= e($a["name"]) ?>, published <?= date("d M Y", strtotime($a["publish_date"])) ?></small>
<p><?= nl2br(e($a["body"])) ?></p>
<h4>Comments (<?= $comments->num_rows ?>)</h4>
<?php foreach ($comments as $c): ?>
  <div class="art"><b><?= e($c["name"]) ?></b> <small><?= date("d M H:i", strtotime($c["comment_date"])) ?></small><br>
  <?= e($c["comment"]) ?></div>
<?php endforeach; ?>
<?php if (user()): ?>
  <form method="post"><textarea name="comment" id="comment" rows="2" cols="50"></textarea><br>
  <input type="submit" value="Add comment" id="go"></form>
<?php else: ?>
  <p><a href="login.php">Log in</a> to comment.</p>
<?php endif; ?>
<?php require "inc/footer.php"; ?>

Asha logs in again, opens her published article and adds a comment:

OUTPUT browser · article.php?id=1 after Asha's comment
Browser output

A visitor who is not logged in can read the article and the comments but sees no comment form:

OUTPUT browser · article.php?id=1 as a guest
Browser output
Step 9 – User Administration
admin.php
<?php
require "inc/config.php";
require_level(3);                                    // administrators only
if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $uid = (int) $_POST["user_id"];
    $lvl = (int) $_POST["access_level"];
    if (isset(LEVELS[$lvl]) && $uid != user()["user_id"]) {      // cannot demote yourself
        $db->execute_query("UPDATE cms_users SET access_level = ? WHERE user_id = ?", [$lvl, $uid]);
        flash("Access level changed.");
    }
    header("Location: admin.php");
    exit;
}
$users = $db->query("SELECT u.user_id, u.name, u.email, u.access_level,
                     (SELECT COUNT(*) FROM cms_articles a WHERE a.user_id = u.user_id) AS articles
                     FROM cms_users u ORDER BY u.user_id");
$title = "Admin";
require "inc/header.php";
?>
<h3>Users</h3>
<table border="1" cellpadding="4" style="border-collapse:collapse">
<tr><th>Id</th><th>Name</th><th>E-mail</th><th>Articles</th><th>Access level</th></tr>
<?php foreach ($users as $u): ?>
<tr><td><?= $u["user_id"] ?></td><td><?= e($u["name"]) ?></td><td><?= e($u["email"]) ?></td>
  <td align="center"><?= $u["articles"] ?></td>
  <td><form method="post" style="margin:0">
        <input type="hidden" name="user_id" value="<?= $u["user_id"] ?>">
        <select name="access_level" id="lvl<?= $u["user_id"] ?>">
          <?php foreach (LEVELS as $k => $v): ?>
            <option value="<?= $k ?>" <?= $k == $u["access_level"] ? "selected" : "" ?>><?= $v ?></option>
          <?php endforeach; ?>
        </select> <input type="submit" value="Save" id="save<?= $u["user_id"] ?>">
      </form></td></tr>
<?php endforeach; ?>
</table>
<?php require "inc/footer.php"; ?>

The administrator promotes Asha to moderator:

OUTPUT browser · admin.php after making Asha a moderator
Browser output

Finally, the home page as every visitor sees it:

OUTPUT browser · index.php as a guest
Browser output
Testing the Security
OUTPUT browser · a guest opens admin.php directly (redirected to login.php)
PHP CMS   Home Login Register
Login
E-mail  Password
© 2026 PHP CMS
SQL SESSION SQL typed in the MariaDB monitor (XAMPP Shell → mysql -u root) or the phpMyAdmin SQL tab
MariaDB [cms]> SELECT user_id, name, LEFT(password, 7) AS hash_start, access_level FROM cms_users;
+---------+------------+------------+--------------+
| user_id | name       | hash_start | access_level |
+---------+------------+------------+--------------+
|       1 | Site Admin | $2y$10$    |            3 |
|       2 | Asha Verma | $2y$10$    |            2 |
+---------+------------+------------+--------------+
2 rows in set

MariaDB [cms]> SELECT article_id, is_published, title FROM cms_articles;
+------------+--------------+--------------------------------------------+
| article_id | is_published | title                                      |
+------------+--------------+--------------------------------------------+
|          1 |            1 | Why every student should learn PHP in 2026 |
+------------+--------------+--------------------------------------------+
1 row in set

MariaDB [cms]> SELECT comment_id, article_id, user_id, LEFT(comment, 32) AS comment FROM cms_comments;
+------------+------------+---------+----------------------------------+
| comment_id | article_id | user_id | comment                          |
+------------+------------+---------+----------------------------------+
|          1 |          1 |       2 | Thanks for publishing! Next arti |
+------------+------------+---------+----------------------------------+
1 row in set

The passwords are stored only as bcrypt hashes, roles are stored as access levels, and every page checks the level with require_level().

Ideas for Extending the Project
  • Add categories and a search box (a LIKE query with a prepared statement).
  • Add pagination to the home page with LIMIT offset, count (Chapter 32).
  • Let moderators delete comments and authors edit their own pending articles.
  • Upload an image with each article (Chapter 14) and store the file name in the table.
  • Provide an RSS feed of the latest articles in XML (Chapter 38) or a JSON API (Chapter 39).
  • Protect forms against Cross-Site Request Forgery with a random token stored in the session.
Key Takeaways
  • The CMS uses three tables – users, articles (pending/published) and comments – linked by foreign keys.
  • inc/config.php holds the connection, session start and helpers (e(), user(), require_level(), flash()).
  • Passwords are stored with password_hash() and checked with password_verify(); the session id is regenerated at login.
  • Articles are inserted as pending; moderators publish them with an UPDATE that sets is_published and publish_date.
  • All outside data reaches the database through prepared statements (execute_query()), and all output is escaped with htmlspecialchars().
  • Access levels decide which menu items appear and which pages may be opened.
Program File

Extract the ZIP into C:\xampp\htdocs. It creates the folder phpbook/p1 used in the file labels above; open the programs at http://localhost/phpbook/… with Apache (and MySQL, where needed) started in the XAMPP Control Panel.

Question / Answer (15 questions)

Answer : cms_users, cms_articles and cms_comments. Each article has a user_id (author) referencing cms_users; each comment has an article_id referencing cms_articles and a user_id referencing cms_users.

Answer : In the access_level column: 1 = user (author), 2 = moderator, 3 = administrator.

Answer : When an article is deleted, all comments that reference it are deleted automatically by the database.

Answer : session_start(), the time zone, the MySQLi connection and helper functions. Every page requires it, so these settings and functions are written only once.

Answer : It escapes text with htmlspecialchars() before it is output, protecting the pages against cross-site scripting.

Answer : It checks whether a user is logged in and has at least the required access level; if not, it redirects to login.php and stops the script.

Answer : At registration it is stored as password_hash($pass, PASSWORD_DEFAULT); at login the hash is fetched by e-mail and checked with password_verify().

Answer : To give the logged-in session a new id, preventing session fixation attacks.

Answer : The session only needs the id, name, e-mail and level; keeping the hash out of the session limits the damage if session data is ever exposed.

Answer : It is inserted with is_published = 0 (pending). A moderator publishes it in review.php, which sets is_published = 1 and publish_date = NOW().

Answer : Only published articles (is_published = 1), newest first, with author, publish date and number of comments.

Answer : After processing a POST (adding a comment) the script redirects to the article page with GET, so refreshing the page does not submit the comment again.

Answer : A one-time message stored in the session by one request (e.g. "Article published.") and displayed and removed by the next page.

Answer : Every query that contains outside data uses a prepared statement through $db->execute_query() with ? placeholders; numeric ids are also cast with (int).

Answer : Categories and search, pagination of the home page, image uploads for articles, comment moderation, an RSS/JSON feed, or CSRF tokens on forms (any three).
MCQ Test : Check Your Understanding (5 questions)

Select an option and press Check Answer, or answer all questions and press Submit Test to see your score. Each answer comes with an explanation.

Q1. In the project, an access_level of 2 means:

Show Answer

Correct Answer : C. Moderator
1 = user, 2 = moderator, 3 = administrator.

Q2. A newly composed article is stored with:

Show Answer

Correct Answer : B. is_published = 0
It stays pending until a moderator publishes it.

Q3. Which function protects pages by role in the project?

Show Answer

Correct Answer : C. require_level()
It redirects users without the required level.

Q4. What prevents a comment from being posted twice when the page is refreshed?

Show Answer

Correct Answer : B. The Post/Redirect/Get pattern
After the POST the browser is redirected to a GET page.

Q5. Which statement publishes an article?

Show Answer

Correct Answer : B. UPDATE cms_articles SET is_published = 1, publish_date = NOW() WHERE article_id = ?
Publishing updates the status and publish date.

Bibliography/References

The notes on this page are the author’s own compilation from the e-book PHP: Basics to Advance. Every program was run on Apache with PHP 8 and MariaDB, and the output shown is the real result of that run. The works listed below were consulted and are cited for attribution and further reading. Product names and trademarks belong to their respective owners.

  1. The PHP Group. (n.d.). PHP Manual – mysqli::execute_query. https://www.php.net/manual/en/mysqli.execute-query.php
  2. The PHP Group. (n.d.). PHP Manual – Password Hashing. https://www.php.net/manual/en/book.password.php
  3. The PHP Group. (n.d.). PHP Manual – Sessions. https://www.php.net/manual/en/book.session.php
  4. OWASP Foundation. (n.d.). Cross-Site Request Forgery Prevention Cheat Sheet. https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html
  5. MariaDB Foundation. (n.d.). FOREIGN KEY. MariaDB Knowledge Base. https://mariadb.com/kb/en/foreign-keys/