スレッド機能を実装した掲示板です。スレッドの追加と投稿は誰でもできますが、スレッドと投稿の削除は管理者のパスワードがないとできない仕様です。削除した投稿やスレッドは、DB上に残っているのでSQLを使って復元できるようになっています。
環境
OS ubuntu22.04
Webサーバ apache2.4.52(自身のIP 192.168.19.128、公開フォルダ /var/www/html)
SQLite 3.37.2
PHP 8.1.2-1ubuntu2.14
DBの作成
掲示板のデータを格納するDBを作成するSQLは次の通りです。
$ > sqlite3 bbs2.db
sqlite > CREATE TABLE threads (id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT, created_by TEXT, is_deleted INTEGER);
sqlite > CREATE TABLE posts (id INTEGER PRIMARY KEY AUTOINCREMENT, thread_id INTEGER, content TEXT, created_by TEXT, is_deleted INTEGER, posts_id integer);
sqlite > CREATE TABLE admin (password TEXT);
threads スレッドデータの格納用 is_deleted が削除フラグ
posts が掲示板データの格納用 is_deleted が削除フラグ
admin が削除用管理者パスワード保存用
パスワードはSHA-256によるハッシュ値で保存しているので、万が一データベースを見られてもパスワードはわかりません。
出力されたbbs2.dbファイルは、次のフォルダに置いてパーミッションを707に変更しました。
/var/www/db/
PHPプログラムの作成(管理者パスワード設定画面)
今回は、管理者用の画面から作成します。
次のPHPプログラムを配置しました。
ファイル名 admin.php
<?php
$db = new SQLite3('/var/www/db/bbs2.db');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['new_password'])) {
$new_password = $_POST['new_password'];
// 新しいパスワードをハッシュ化して保存する
$hashed_password = password_hash($new_password, PASSWORD_DEFAULT);
$db->exec("INSERT OR REPLACE INTO admin (password) VALUES ('$hashed_password')");
}
// 現在の管理者パスワードを取得する
$current_password = $db->querySingle("SELECT password FROM admin");
// HTMLを出力する
?>
<!DOCTYPE html>
<html>
<head><title>管理者用パスワードの設定</title></head>
<body>
<h1>管理者用パスワードの設定</h1>
<form method="post" action="admin.php">
<label for="new_password">新しいパスワード:</label>
<input type="password" id="new_password" name="new_password" required />
<input type="submit" value="パスワードを変更" />
</form>
<p>現在のパスワード: <?php echo $current_password ? $current_password : "未設定"; ?></p>
</body>
</html>
このプログラムは、管理者用のパスワードを設定するためのページです。
前のパスワードを知らなくても新しいパスワードを設定できるので、アクセス制限がかかっているページに置かないと危険です。
次の場所に配置し、パーミッションは705に設定しました。
/var/www/html/php
PHPプログラムの作成(スレッド表示画面)
続いてスレッド表示用の画面を作成します。
次のPHPプログラムを配置しました。
ファイル名 thread.php
<?php
$db = new SQLite3('/var/www/db/bbs2.db');
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
if (isset($_POST['thread_title'])) {
$thread_title = $_POST['thread_title'];
// 新しいスレッドを追加する
$stmt = $db->prepare('INSERT INTO threads (title) VALUES (:title)');
$stmt->bindValue(':title', $thread_title, SQLITE3_TEXT);
$stmt->execute();
} elseif (isset($_POST['delete_thread_id']) && isset($_POST['admin_password'])) {
$admin_password = $_POST['admin_password'];
$delete_thread_id = $_POST['delete_thread_id'];
// 管理者パスワードが正しいか確認する
$admin_check = $db->querySingle("SELECT password FROM admin");
if (password_verify($admin_password, $admin_check)) {
// スレッドを削除する
$db->exec("update threads set is_deleted = 1 WHERE id = $delete_thread_id");
$db->exec("update posts set is_deleted = 1 WHERE thread_id = $delete_thread_id");
}
}
}
// スレッド一覧を取得する
$threads = $db->query('SELECT * FROM threads where is_deleted is null');
// HTMLを出力する
?>
<!DOCTYPE html>
<html>
<head><title>掲示板</title></head>
<body>
<h1>スレッド一覧</h1>
<table border=1 width="700px"><tr><td>
<?php while ($thread = $threads->fetchArray()) : ?>
<a href="posts.php?thread_id=<?= $thread['id'] ?>"> <?= $thread['id'] ?>: <?= $thread['title'] ?></a>
<?php endwhile; ?>
</td></tr></table>
</ul>
<h2>新しいスレッドを追加</h2>
<form method="post" action="thread.php">
<input type="text" name="thread_title" placeholder="スレッドのタイトル" required style="width:400px;"/>
<input type="submit" value="スレッドを追加" />
</form>
<h2>スレッドの削除</h2>(スレッドの削除には管理者パスワードが必要です)<br>
<form method="post" action="thread.php">
削除したいスレッドID<input type="text" name="delete_thread_id" value="" style="width:30px;" />
管理者用パスワード<input type="password" name="admin_password" placeholder="削除用パスワード" />
<input type="submit" value="スレッド削除" />
</form>
</body>
</html>
このページはスレッドの一覧表示と登録画面です。管理者パスワードがあればスレッドを削除することができるようになっています。
スレッドを削除すると、スレッドと紐づいている投稿の is_deleted に1を入れる仕様になっていますので、削除したスレッドの復元はSQLで簡単に行えます。
参考までにスレッド番号「14」を復元するSQLを書いておきます。
$ > sqlite3 bbs2.db
sqlite > update threads set is_deleted = 0 WHERE id =14;
sqlite > update posts set is_deleted = 0 WHERE thread_id = 14;
PHPプログラムの作成(投稿表示画面)
続いて投稿表示用の画面を作成します。
次のPHPプログラムを配置しました。
ファイル名 posts.php
<?php
$db = new SQLite3('/var/www/db/bbs2.db');
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['content']) && isset($_POST['thread_id'])) {
$content = $_POST['content'];
$thread_id = $_POST['thread_id'];
$created_by = $_POST['created_by'];
//投稿番号を生成
$stmt = $db->prepare('SELECT (COALESCE(MAX(posts_id), 0) + 1)as maxcount from posts where thread_id = :thread_id');
$stmt->bindValue(':thread_id', $thread_id, SQLITE3_INTEGER);
$post = $stmt->execute();
$max_count = 1;
if ($row = $post->fetchArray()) {
$max_count = $row['maxcount'];
}
$stmt = $db->prepare('INSERT INTO posts (thread_id, content, created_by, is_deleted,posts_id) VALUES (:thread_id, :content, :created_by, 0, :posts_id)');
$stmt->bindValue(':thread_id', $thread_id, SQLITE3_INTEGER);
$stmt->bindValue(':content', $content, SQLITE3_TEXT);
$stmt->bindValue(':created_by', $created_by, SQLITE3_TEXT);
$stmt->bindValue(':posts_id', $max_count, SQLITE3_TEXT);
$stmt->execute();
} elseif (isset($_POST['delete_posts_id']) && isset($_POST['admin_password']) && isset($_POST['thread_id'])) {
$admin_password = $_POST['admin_password'];
$delete_posts_id = $_POST['delete_posts_id'];
$thread_id = $_POST['thread_id'];
// 管理者パスワードが正しいか確認する
$admin_check = $db->querySingle("SELECT password FROM admin");
if (password_verify($admin_password, $admin_check)) {
// スレッドを削除する
$db->exec("UPDATE posts set is_deleted = 1 WHERE thread_id = $thread_id and posts_id = $delete_posts_id");
}
}
//スレッド番号はPOST,GETどちらでも取得可能
if (isset($_GET['thread_id'])) {
$thread_id = $_GET['thread_id'];
} elseif(isset($_POST['thread_id'])) {
$thread_id = $_POST['thread_id'];
}
if (isset($thread_id)) {
// 対応するスレッドの投稿を取得する
$stmt = $db->prepare('SELECT * FROM posts WHERE thread_id = :thread_id AND is_deleted = 0');
$stmt->bindValue(':thread_id', $thread_id, SQLITE3_INTEGER);
$posts = $stmt->execute();
$thread_title = $db->querySingle("SELECT title FROM threads WHERE id = $thread_id");
}
if(isset($thread_title)){
// 投稿後に投稿記事一覧を表示
echo "<table border=1 width='700px'><tr><td>";
echo "<h1>$thread_id: $thread_title</h1>";
while ($post = $posts->fetchArray()) {
echo $post['posts_id']." 名前: " . $post['created_by']."<br>" ;
echo "<input type='hidden' name='thread_id' value='$thread_id' />";
echo "<dd>".$post['content']."</dd><br>";
}
echo "</td></tr></table>";
// 投稿フォームを表示
echo "<h2>新しい投稿を追加</h2>";
echo "<form method='post' action='posts.php'>";
echo " <input type='hidden' name='thread_id' value='$thread_id' width='40px'/>";
echo " <input type='text' name='created_by' placeholder='投稿者名' required /><br><br>";
echo " <textarea name='content' placeholder='投稿内容' required></textarea><br><br>";
echo " <input type='submit' value='投稿' />";
echo "</form>";
echo "<h2>スレッドの削除</h2>(スレッドの削除には管理者パスワードが必要です)<br>";
echo "<form method='post' action='posts.php'>";
echo " 削除したい投稿ID<input type='text' name='delete_posts_id' value='' style='width:30px;' />";
echo " <input type='hidden' name='thread_id' value='$thread_id' />";
echo " 管理者用パスワード<input type='password' name='admin_password' placeholder='削除用パスワード' />";
echo " <input type='submit' value='スレッド削除' />";
echo "</form>";
}else {
echo "<h2>スレッドが見つかりません。</h2>";
}
echo "<a href='./thread.php'>スレッド一覧に戻る</a>";
?>
このPHPはスレッドに紐づいている投稿の一覧表示と投稿画面です。管理者パスワードがあれば投稿を削除することができるようになっています。
削除した投稿の is_deleted に1を入れる仕様になっていますので、復元するには次のSQLで簡単に行えます。
参考までにスレッド番号「thread_id =7」の投稿番号「posts_id =11」を復元するSQLを書いておきます。
$ > sqlite3 bbs2.db
sqlite > update posts set is_deleted = 0 WHERE thread_id = 7 and posts_id = 11;
posts_idだけを指定すると、すべてのスレッド上の同じposts_idを指定することになるので、必ずthread_idとposts_idを指定するようにしてください。
作成したら、thread.php、posts.php、admin.php(ほかの場所に配置してもOK)を
/var/www/html/php
に配置して、パーミッションを705に変更します。
実行
まずは管理者ページでブラウザから管理者パスワードを設定します。
http://192.168.19.128/php/admin.php
admin.phpを実行した様子
パスワードのハッシュ値が表示されています。
続いて、http://192.168.19.128/php/thread.phpを開くとこんな感じになります。
thread.phpを実行した様子
スレッド名からリンク先を開くと、posts.phpが開きます。
posts.phpを実行した様子
ここでは記事の投稿と閲覧ができます。
CSSを使ったりHTMLを工夫すれば見た目もまともになると思います。