phpな休日 BBS sitemap
ゲストブック

ゲストブックを作成します。
入力項目は [名前]・[メールアドレス]・[URL]・[タイトル]・[コメント] で、その他にキーとなる項目として [通しナンバー]、あとは [投稿日時]・[ホスト情報] とします。
ログデータの順序は [通しナンバー]・[名前]・[メールアドレス]・[URL]・[タイトル]・[コメント]・[投稿日時]・[ホスト情報]、
変数名は [$no]・[$name]・[$email]・[$url]・[$title]・[$comment]・[$time]・[$iphost]、これを基本にログから読んだ最新の一行の場合は b を付けて $bno などとしています。
機能的には [一行掲示板] +αで、普通に長いコメントが書ける程度のものを想定します。大きな変更点はログに [通しナンバー] を記入すること。これはログ編集をする場合に行を特定するのに必要になります。最初が 1 で次の行は +1 していくカウンタ方式になります。
$no によって行を特定出来るようになるので、[管理者削除] 機能を追加します。
$no の数値ですが、最初にログファイルを開いて fgets で1行読み込み(その行のナンバーを $bno として)、+1処理します。

$data = fopen ("./gb.cgi","r"); //ファイルを読みこみ専用でオープン
$bdata = fgets ($data); //1行読み込む
list($bno,$bname,$bemail,$burl,$btitle,$bcomment,$btime,$biphost) = explode("<>",$bdata); //変数に変換
fclose($data); //ファイルを閉じる
$no = $bno; //$bno(読み込んだ一番上の行の数値)を $no に格納
$no++; //+1したものを今回の記事ナンバーとする

公開された場所に設置するわけですので、スパム対策を余儀なくされるのですが、今回とる対策は
・コメント内にURLが書かれていたら投稿拒否。
・投稿者のURL記入欄に h を抜かして ttp~ 書いてもらう。
・それでも不本意な投稿があった場合、管理者権限で削除する。
の3つにします。
1つめはすでに [一行掲示板] で経験済みです。2つめは投稿後の処理で先頭に h を付加します。 ttp://www.p-ho.net の投稿があった場合、http://www.p-ho.net に置き換えます。
スパムのほとんどはURLを記入してきますので、効果が期待できます。

$url = "h" . $url;

3つめの記事削除の時、削除記事の行を特定するのに役立つのが増やした項目 $no です。
ページの下にもうひとつFORMを設置します。

<form action="./guestbook.php" method="POST">
no. <input size="10" name="delno"> pass <input type="password" size="10" name="pass"> <input type="submit" value="管理者削除">
</form>

イメージは

no.  pass  
こんな感じです。(↑は動作しません)
その上で、スクリプトの最初で削除する記事番号を受け取って処理します。

if (isset($_POST["delno"])) { //削除ナンバーが送られてきた場合
$pass = $_POST["pass"]; //POSTの値を受け取って変数に割り当てる
$delno = $_POST["delno"]; //POSTの値を受け取って変数に割り当てる
if ($pass == "password") { //パスワードが正しければ(パスワードがpasswordの場合)
$del = file("./gb.cgi"); //ログファイルを開いて
for ($i = 0; $i < count($del); $i++) { //1行づつ走査しながら展開する
$delline = explode("<>", $del[$i]);
if ($delline[0] == $delno) { //$delnoを頼りに対象行を探す
array_splice($del, $i, 1); //対象行が見つかったら1行削除(ログファイル上はまだ削除されない)
}
}
$dellog = fopen("./gb.cgi", "w"); //書き込みモードでデータを開く(空になります)
flock($dellog, LOCK_EX); //ファイルロック
foreach($del as $value) { //上($del)で処理(1行削除)したデータを配列に入れて
fputs($dellog, $value); //書き込む
}
flock($dellog, LOCK_UN); //ファイルロック解除
fclose($dellog); //ファイルを閉じる
}
}

初出の foreach ですが、配列をカンタンに処理してくれます。foreach($del as $value) このように書けば、上の for で処理した結果($del)を $value に置き換えてくれます。

以下にゲストブックの全スクリプトを記述します。

<?php
mb_language("ja");
mb_internal_encoding("UTF-8");
if (isset($_POST["delno"])) { //削除ナンバーが送られてきた場合
$pass = $_POST["pass"]; //POSTの値を受け取って変数に割り当てる
$delno = $_POST["delno"]; //POSTの値を受け取って変数に割り当てる
if ($pass == "password") { //パスワードが正しければ
$del = file("./gb.cgi"); //ログファイルを開いて
for ($i = 0; $i < count($del); $i++) { //1行づつ走査しながら展開する
$delline = explode("<>", $del[$i]);
if ($delline[0] == $delno) { //$delnoを頼りに対象行を探す
array_splice($del, $i, 1); //対象行が見つかったら1行削除(ログファイル上はまだ削除されない)
}
}
$dellog = fopen("./gb.cgi", "w"); //書き込みモードでデータを開く(空になります)
flock($dellog, LOCK_EX); //ファイルロック
foreach($del as $value) { //上($del)で処理(1行削除)したデータを配列に入れて
fputs($dellog, $value); //書き込む
}
flock($dellog, LOCK_UN); //ファイルロック解除
fclose($dellog); //ファイルを閉じる
}
}
if (isset($_GET["line"])) {$line = $_GET["line"];} //GETで渡された番号を $line に代入
if (($_POST["name"] != "") or ($_POST["comment"] != "")) { //もしPOSTに [name] か [comment] があれば
if ((!preg_match ("{(https?)(://[[:alnum:]¥+¥$¥;¥?¥.%,!#~*/:@&=_-]+)}", $_POST["url"])) && (!preg_match ("{(https?)(://[[:alnum:]¥+¥$\;¥?¥.%,!#~*/:@&=_-]+)}", $_POST["comment"]))) { //URL内とコメント内にURLがなければ
$data = fopen ("./gb.cgi","r"); //ファイルを読みこみ専用でオープン
$bdata = fgets ($data); //1行読み込む
list($bno,$bname,$bemail,$burl,$btitle,$bcomment,$btime,$biphost) = explode("<>",$bdata); //変数に変換
fclose($data); //ファイルを閉じる
$no = $bno; //$bno(読み込んだ一番上の行の数値)を $no に格納
$no++; //+1したものを今回の記事ナンバーとする
if (isset($_POST["name"])) { //もしPOSTに [name] があれば
$name = $_POST["name"]; //POSTのデータを変数$nameに格納
if( get_magic_quotes_gpc() ) { $name = stripslashes("$name"); } //クォートをエスケープする
$name = htmlspecialchars ($name); //HTMLタグ禁止
$name = mb_strimwidth ($name, 0, 14, "", "UTF-8"); //長いデータを14バイトでカット
}
if (isset($_POST["email"])) { //もしPOSTに [email] があれば
$email = $_POST["email"]; //POSTのデータを変数$emailに格納
if( get_magic_quotes_gpc() ) { $email = stripslashes("$email"); } //クォートをエスケープする
$email = htmlspecialchars ($email); //HTMLタグ禁止
$email = mb_strimwidth ($email, 0, 50, "", "UTF-8"); //長いデータを50バイトでカット
}
if (isset($_POST["url"])) { //もしPOSTに [url] があれば
$url = $_POST["url"]; //POSTのデータを変数$urlに格納
if( get_magic_quotes_gpc() ) { $url = stripslashes("$url"); } //クォートをエスケープする
$url = htmlspecialchars ($url); //HTMLタグ禁止
$url = mb_strimwidth ($url, 0, 200, "", "UTF-8"); //長いデータを200バイトでカット
$url = "h" . $url;
}
if (isset($_POST["title"])) { //もしPOSTに [title] があれば
$title = $_POST["title"]; //POSTのデータを変数$titleに格納
if( get_magic_quotes_gpc() ) { $title = stripslashes("$title"); } //クォートをエスケープする
$title = htmlspecialchars ($title); //HTMLタグ禁止
$title = mb_strimwidth ($title, 0, 100, "", "UTF-8"); //長いデータを100バイトでカット
}
if (isset($_POST["comment"])) { //もしPOSTに [comment] があれば
$comment = $_POST["comment"]; //POSTのデータを変数$commentに格納
if( get_magic_quotes_gpc() ) { $comment = stripslashes("$comment"); } //クォートをエスケープする
$comment = htmlspecialchars ($comment); //HTMLタグ禁止
$comment = mb_strimwidth ($comment, 0, 10000, "", "UTF-8"); //長いデータを10000バイトでカット
$comment = str_replace("¥r¥n", "¥r", $comment); //Windowsの改行コードを置き換え
$comment = str_replace("¥r", "¥n", $comment); //Machintoshの改行コードを置き換え
$mbcomment = $comment; //メール用に($mbcomment)として確保
$comment = str_replace("¥n", "<br>", $comment); //¥nを<br>に変換(保存・表示用)
}
if (($bname != $name) or ($bcomment != $comment)){ //name か comment が違っていたら
$time = date("Y/n/j G:i"); //日時の取得
$ip = getenv("REMOTE_ADDR"); //IPアドレス取得
$host = gethostbyaddr(getenv("REMOTE_ADDR")); //HOST取得
$iphost = ($host . "/" . $ip);
//mb_send_mail ("hoge@hoge.net", "ゲストブックより", "$name 様¥n$email¥n$url¥n$title¥n$mcomment¥n$time¥n$iphost¥n");
$topwrite = "$no<>$name<>$email<>$url<>$title<>$comment<>$time<>$iphost<>¥n";//新しく追加するデータを <> で区切って整形
$log = file("./gb.cgi"); //ファイル1行づつ配列に入れながら読み込む
$write = fopen("./gb.cgi","w"); //書き込み用モードでデータを開く(データは空になる)
flock($write, LOCK_EX); //ファイルロック開始
fputs($write, $topwrite); //先頭に1行書き込む
for($i = 0; $i < 999; $i++){ //999行まで走査処理をする
fputs($write, $log[$i]); //今までの分を書き込む
}
flock ($write, LOCK_UN); //ファイルロック解除
fclose ($write); //ファイルを閉じる
}
}
}
?>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>p-ho guestbook</title>
</head>
<body>
※メールアドレスを入力しても画面には表示はされません。(管理人には伝わります)<br>
※ url は頭の h を抜かして ttp~ 書いてください。<br>
※コメント内にURLを記入すると投稿できません。(スパム対策)
<form action="./guestbook.php" method="POST">
<table border="0" cellspacing="0" cellpadding="2">
<tr>
<td>name</td>
<td><input size="40" name="name"></td>
</tr>
<tr>
<td>email</td>
<td><input size="68" name="email"></td>
</tr>
<tr>
<td>url</td>
<td><input size="68" name="url"></td>
</tr>
<tr>
<td>title</td>
<td><input size="68" name="title"></td>
</tr>
<tr>
<td>comment </td>
<td><textarea name="comment" rows="6" cols="66"></textarea></td>
</tr>
</table>
<input type="submit" value="送信">
</form>
<?php
$log = file("./gb.cgi"); //ログファイルを読み込みます
if (!$line) {
$line = 0; //最初のページは 0
}
$pageline = 5; //1ページに表示させる行数
print "<hr>"; //最初に線を一本表示
for ($i = $line; $i < $line+$pageline; $i++) { //1行づつ走査しながら1ページ分を表示します
$list = explode("<>",$log[$i]);
if($list[0] != ""){
print $list[4] . " ";;
print $list[1] . " ";
if ($list[3]){ //urlに入力があった場合
print "<a href='" . $list[3] . "' target='_blank'>url</a> ";
}
print $list[6] . " [" . $list[0] . "]<br>";
print $list[5];
print "<hr>"; //記事の最後に線を表示
}
}
$totalline = (count($log)); //総行数
$totalpage = ceil(($totalline / $pageline)); //総ページ数(小数点以下切り上げ)
if ($totalline > $pageline) { //2ページ以上になるなら
$back = $totalline; //最初の $back は総行数
$now = 1; //ページ番号は1から
$next = 0; //行番号に代入される値は0から
print "&nbsp;";
for ($i = $line; $back >= 1; $i++) { //1行づつ走査しながら1ページ分を表示します
if ($line == $next) {
print $now . "&nbsp;";
}
else {
print "<a href='guestbook.php?line=" . $next . "'>" . $now . "</a>&nbsp;"; //GETで渡すリンク(行番号)
}
$now++; //ページ番号を増やしていく
$next = $next + $pageline; //行番号を増やしてい
$back = $back - $pageline; //行番号を減らしてい
}
}
?>
<form action="./guestbook.php" method="POST">
no. <input size="10" name="delno"> pass <input type="password" size="10" name="pass"> <input type="submit" value="管理者削除">
</form>
</body>
</html>

実行 //ページ内表示の関係で上記ソースそのままではありません。

うまくスクリプトが書けない方、すぐには環境(エディタなど)が整わない方、とにかく設置して動作を見たい方のために こちら からzip形式をダウンロードできるようにしました。

2014.2.4 last edit

Produced by haku