I'm attempting to create a basic wiki for personal use. I've got a pretty good start, but the code is beginning to become cumbersome to modify, and some of the things I'm doing feel very fragile. I've heard and looked into using the MVC pattern, but I'm having trouble understanding how I would even implement my current project using that pattern. If at all I'd like to avoid suggestions such as "use a framework," as my goal is to learn how to do things by hand.
Highlights include:
- Trying to cut down on edit.php. There's two forms that are pretty much exactly the same, but call slightly different database queries.
- Making sure my routing is solid with the
/wiki.php?title=article-name
format (although I'd love to figure out how to properly implement a front controller). - I'm pretty sure I don't have any error-checking going on like I should.
index.php
<?php
session_start();
require_once "database.php";
/**
* TODO: Basic user authentication.
* TODO: Categories for articles.
* TODO: Articles broken down into sections that can be edited independently?
* TODO: Make links to articles that don't exist a different color?
*/
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Webdev</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php if (isset($_SESSION["message"])): ?>
<p><?= htmlspecialchars($_SESSION["message"]); ?></p>
<?php endif; ?>
<?php unset($_SESSION["message"]); ?>
<div><a href="/edit.php">[Create Article]</a> <a href="/documentation.php">[Documentation]</a></div>
<?php
$stmt = $pdo->query("SELECT title, slug FROM articles");
$articles = $stmt->fetchAll();
?>
<?php if ($articles): ?>
<ul>
<?php foreach ($articles as $article): ?>
<li><a href="/wiki.php?title=<?= $article["slug"]; ?>"><?= htmlspecialchars($article["title"]); ?></a></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<script src="main.js"></script>
</body>
</html>
wiki.php
<?php
session_start();
require_once "vendor/autoload.php";
require_once "database.php";
function slugify($str) {
$str = strtolower($str);
$str = trim($str);
$str = preg_replace("/[^A-Za-z0-9 -]/", "", $str);
$str = preg_replace("/\s+/", " ", $str);
$str = str_replace(" ", "-", $str);
return $str;
}
$ParsedownExtra = new ParsedownExtra();
$ParsedownExtra->setSafeMode(true);
$title = htmlspecialchars($_GET["title"]);
$slug = "";
if ($title !== slugify($title)) {
$slug = slugify($title);
} else {
$slug = $title;
}
$stmt = $pdo->prepare("SELECT id, title, slug, body FROM articles WHERE slug = ?");
$stmt->execute([$slug]);
$article = $stmt->fetch();
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Webdev | Wiki - <?= $article["title"] ?? "Article"; ?></title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php if (isset($_SESSION["message"])): ?>
<p><?= htmlspecialchars($_SESSION["message"]); ?></p>
<?php endif; ?>
<?php unset($_SESSION["message"]); ?>
<?php if ($article): ?>
<div><a href="/">[Home]</a> <a href="/edit.php?title=<?= $article["slug"]; ?>">[Edit Article]</a> <a href="/documentation.php">[Documentation]</a></div>
<?= $ParsedownExtra->text($article["body"]); ?>
<?php else: ?>
<div><a href="/">[Home]</a></div>
<p>Unknown article ID. <a href="/edit.php?title=<?= $title; ?>">[Create Article]</a></p>
<?php endif; ?>
<script src="main.js"></script>
</body>
</html>
edit.php
<?php
session_start();
require_once "database.php";
function slugify($str) {
$str = strtolower($str);
$str = trim($str);
$str = preg_replace("/[^A-Za-z0-9 -]/", "", $str);
$str = preg_replace("/\s+/", " ", $str);
$str = str_replace(" ", "-", $str);
return $str;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Webdev | Edit</title>
<link href="style.css" rel="stylesheet">
</head>
<body>
<?php if (!empty($_SESSION["message"])): ?>
<p><?= htmlspecialchars($_SESSION["message"]); ?></p>
<?php endif; ?>
<?php unset($_SESSION["message"]); ?>
<div><a href="/">[Home]</a> <a href="/documentation.php">[Documentation]</a></div>
<?php
$errors = [];
$title = htmlspecialchars($_GET["title"] ?? "");
$slug = "";
if ($title !== slugify($title)) {
$slug = slugify($title);
} else {
$slug = $title;
}
$stmt = $pdo->prepare("SELECT id, title, slug, body FROM articles WHERE slug = ?");
$stmt->execute([$slug]);
$article = $stmt->fetch();
?>
<?php if ($article): ?>
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!empty($_POST["edit-article"])) {
$title = $_POST["title"];
$body = $_POST["body"];
$slug = slugify($title);
if (empty(trim($title))) {
$errors[] = "No title. Please enter a title.";
} elseif (strlen($title) > 32) {
$errors[] = "Title too long. Please enter a title less than or equal to 32 characters.";
} elseif (slugify($title) !== $article["slug"]) {
$errors[] = "Title may only change in capitalization or by having additional symbols added.";
}
if (strlen($body) > 10000) {
$errors[] = "Body too long. Please enter a body less than or equal to 10,000 characters.";
}
if (empty($errors)) {
$stmt = $pdo->prepare("UPDATE articles SET title = ?, body = ? WHERE id = ?");
$stmt->execute([$title, $body, $article["id"]]);
$_SESSION["message"] = "Article successfully updated.";
header("Location: /wiki.php?title=" . $article["slug"]);
exit();
}
}
}
?>
<?php if (!empty($errors)): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?= $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<form action="/edit.php?title=<?= $article["title"]; ?>" method="post" name="form-edit-article">
<div><label for="title">Title</label></div>
<div><input type="text" name="title" id="title" size="40" value="<?= htmlspecialchars($article["title"]); ?>" required></div>
<div><label for="body">Body</label></div>
<div><textarea name="body" id="body" rows="30" cols="120" maxlength="10000"><?= htmlspecialchars($article["body"]); ?></textarea></div>
<div><span id="character-counter"></span></div>
<div><input type="submit" name="edit-article" value="Edit Article"></div>
</form>
<?php else: ?>
<?php
if ($_SERVER["REQUEST_METHOD"] === "POST") {
if (!empty($_POST["create-article"])) {
$title = $_POST["title"];
$body = $_POST["body"];
$slug = slugify($title);
if (empty(trim($title))) {
$errors[] = "No title. Please enter a title.";
} elseif (strlen($title) > 32) {
$errors[] = "Title too long. Please enter a title less than or equal to 32 characters.";
}
$stmt = $pdo->prepare("SELECT title, slug FROM articles WHERE title = ? OR slug = ?");
$stmt->execute([$title, $slug]);
$article_exists = $stmt->fetch();
if ($article_exists) {
$errors[] = "An article by that title already exists. Please choose a different title.";
}
if (strlen($body) > 10000) {
$errors[] = "Body too long. Please enter a body less than or equal to 10,000 characters.";
}
if (empty($errors)) {
$stmt = $pdo->prepare("INSERT INTO articles (title, slug, body) VALUES (?, ?, ?)");
$stmt->execute([$title, $slug, $body]);
$_SESSION["message"] = "Article successfully created.";
header("Location: /wiki.php?title=" . $slug);
exit();
}
}
}
?>
<?php if (!empty($errors)): ?>
<ul>
<?php foreach ($errors as $error): ?>
<li><?= $error; ?></li>
<?php endforeach; ?>
</ul>
<?php endif; ?>
<form action="/edit.php" method="post" name="create-article-form">
<div><label for="title">Title</label></div>
<div><input type="text" name="title" id="title" size="40" value="<?= htmlspecialchars($title); ?>" required></div>
<div><label for="body">Body</label></div>
<div><textarea name="body" id="body" rows="30" cols="120" maxlength="10000"><?= htmlspecialchars($_POST["body"] ?? ""); ?></textarea></div>
<div><span id="character-counter"></span></div>
<div><input type="submit" name="create-article" value="Create Article"></div>
</form>
<?php endif; ?>
<script src="main.js"></script>
</body>
</html>
database.php
<?php
try {
// TODO: Move database to a directory above the root directory.
$pdo = new PDO("sqlite:wiki.db");
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->exec("CREATE TABLE IF NOT EXISTS articles (
id INTEGER PRIMARY KEY,
title TEXT,
slug TEXT,
body TEXT
)");
$pdo->exec("CREATE TABLE IF NOT EXISTS categories (
id INTEGER PRIMARY KEY,
name TEXT
)");
} catch (PDOException $e) {
$error_message = date("Y-m-d G:i:s") . " ERROR: " . $e->getMessage() . "\n";
file_put_contents("pdoerrors.txt", $error_message, FILE_APPEND);
$pdo = null;
}
A-Z
in your negated character class if you have already calledstrtolower()
on the input string? \$\endgroup\$