仕様など
うちのサイトの「ラウンジ」で使うために作成した PHP スクリプトの紹介です。要するにごくシンプルな掲示板です。こんなスクリプトでさえ、参考書と首っ引きで、ウェブで探した情報にも助けられて、やっと書けるような者が作成したものなので、あまりあてになりませんが、自由に使ってください(ただし、問題が起きても作者免責でよろしく)。まずいところがあったら、ご指摘よろしくお願いします。
最近はメールだけでなく、掲示板にもスパムがくるようなので、スパム防衛のあたりも考慮して、仕様を考えました。原始的な方法でスパムよけをしています。また、主義として「できるだけシンプル」なものになっています。
スパムがくる原因として、スクリプト配布元が表示を義務づけている配布元へのリンクがあるようです。検索エンジンを使えば、リンクの文字列から同内容の攻撃が通用するスクリプトがまとめて検索できてしまうわけですから。スクリプト配布元へのリンクは別のリンクページなどにして、掲示板そのものにはスクリプト作者の著作権表示だけにする、というような方式のほうがよいのでは。
- 別途ログファイルを持たず、投稿内容をそのまま HTML 加工して表示用ログに追加していく方式です(原理的には負荷少なめ)。従って、ログ表示部分は "include $data_file;" だけ。
- スクリプトをシンプルにしておくため、スクリプトの行数は最小限で機能は少なめ。
- スクリプトからログのメンテナンスを行なう機能はないので、書き込みの削除は FTP でログファイルをダウンロードし、編集してから再アップロードします。メンテナンス作業を行なう前にサーバのデータファイルを $data_file_2 にリネームしておけば、作業中の書き込みを防げます。また、それ以外の名前にリネームすれば、ログを表示せずに休止状態になります。
- 表示面積節約のため、改行は何行あってもまとめて「 / 」に置き換えます。(ここで使う記号などは自由に変更可。)
- 日本以外のサーバーに置いても、日本時間でタイムスタンプをつけます。
- HTML タグは使えません。
- 本文にリンクらしいものがあると、投稿不可になります。
- リンクを紹介するには、「add link?」とあるリンクをクリックして、そのための入力ウィンドウを表示させます。この入力ウィンドウがいらなくなったときは、「hide」のリンクで消します。この動作のためには、クッキーが許可されている必要があります。なぜこんなことをやっているか、というと、自動処理によるスパムに対して、手順を増やすことで投稿しにくくしているつもりです。思いついたのでやってみたかっただけ。
- 上記の動作は最初にやっておかないと、書きかけの文章が消えます。
- 最初はコメントアウトしてありますが、NG ワードを指定することもできます。
- 入力用のウィンドウの名前(HTML タグの name)を、自動処理スパムスクリプトが掲示板の名前欄、コメント欄などであると推測しにくいものにしてあります。変更もスクリプト先頭あたりの文字列を編集するだけ。
- サイトオーナーだけ、名前を別の色で表示するようになっています。スクリプト先頭の設定部分の $owner_pass および $owner_name を書き換えてください。
- 表示する件数には上限があり、古い投稿は消えていきます。件数の設定は $lines_max の数値で。
- 古い投稿は消えますが、サイトオーナー用には記録ファイルが残ります。HTML タグ入りなので、手作業で切り出せば過去ログとしても使えます。
- 空のデータファイルをあらかじめ設置しておかないと、「メンテナンス中」のメッセージが表示されて書き込めません。
- 投稿するときは、テキストを入力したあと、タブキーを押すとブラウザ上でフォーカスが「送信」ボタンに移動するので、そこでエンターキーを押す、というのが便利です。
動作には、スクリプト本体 (lounge.php) のほかに、補助スクリプト (lounge_2.php)、スタイルシートファイル (lounge.css)、データファイル (lounge.dat)、記録ファイル (lounge_past.dat) が必要です。
PHP を CGI ではなくアパッチモジュールとして動かす場合は、書き込まれるファイル(データファイルおよび記録ファイル)のパーミッション設定が厳格になります。
スクリプト本体
スクリプト内容は次のとおり。色分けは、PHP の highlight_file 関数によるものです。
<?php
// settings ここから
$script_file = 'lounge.php'; // メインスクリプトのファイル名
$script_2 = 'lounge_2.php'; // 補助スクリプトのファイル名
$title = '[ lounge ]'; // 表示ページのタイトル
$data_file = 'lounge.dat'; // ログファイル名
$data_file_2 = 'lounge_.dat'; // メンテナンス時にリネームしておくファイル名
$history_file = 'lounge_past.dat'; // ログを行数制限なしで保存していくファイル
$css_file = 'lounge.css'; // 読み込む CSS ファイル
$owner_pass = '1234'; // オーナー用パスワード
$owner_name = 'motoko'; // 表示するオーナー名
$line_separator = ' / '; // 改行の代わりに使用する文字
$lines_max = 50; // ログの行数
$name_field = 'orange'; // 名前欄のラベル
$text_field = 'pink'; // 発言欄のラベル
$url_field = 'lime'; // URL 欄のラベル
$lock_message = '現在メンテナンス中につき、書き込みはできません。';
// link の一部とみなす文字列を正規表現で羅列
$link_words = 'ttp:\/\/|www\.|\.com|\.net';
// NGワードを正規表現で羅列・有効にするには下の行のコメントをはずす
// $bad_words = 'バカ|アホ';
// settings ここまで
$add = $_COOKIE['lounge_add'];
if ($add) {
$add_link_text = "url: <input type=\"text\" name=\"$url_field\" size=\"40\" /> <a href=\"$script_2\"><< hide</a>";
} else {
$add_link_text = "<a href=\"$script_2\">add link?</a>";
}
$post = $_POST["$text_field"];
if ($post) {
// URL 投稿禁止
if (preg_match("/$link_words/", $post)) {
header("Content-Type: text/html; charset=utf-8");
exit('ERROR!<br />You cannot include web links in your post.<br />投稿内容に URL があると処理できません。');
}
$name = $_POST["$name_field"];
$name = htmlspecialchars($name);
// set name to cookie
setcookie("lounge_name", $name, time() + 60 * 60 * 24 * 60);
// deny bad strings
if ($bad_words && ($name != $owner_pass)) {
if (preg_match("/$bad_words/", $post)) {
header("Content-Type: text/html; charset=utf-8");
exit('ERROR!<br />Data file write failed.<br />データファイルへの書き込み処理に失敗しました。');
}
}
// URL window on/off
$link = $_POST["$url_field"];
if ($link) {
$link = " <a href=\"$link\">link</a> ";
}
// process post
$post = trim($post);
$post = htmlspecialchars($post);
$post = str_replace("\r\n", "\n", $post);
$post = preg_replace("/\n+/", "\n", $post);
$post = str_replace("\n", "$line_separator", $post);
// set time zone to JST
$date = gmdate('(y/m/d H:i)', time()+32400);
$date2 = ' <span class="date">' . $date . '</span>';
$post = $post . $link . $date2;
// name case branching
if ($name == $owner_pass) {
$talker = "<span class=\"ownername\">$owner_name</span>";
} elseif ($name == $owner_name) {
$talker = "<span class=\"badname\">(*^o^*)</span>";
} elseif ($name == '') {
$talker = "<span class=\"name\">ないしょ</span>";
} else {
$talker = "<span class=\"name\">$name</span>";
}
$post = '<p>' . $talker . ': ' . $post . "</p>\n";
// read data into $lines
$lines = file("$data_file");
array_unshift($lines, $post);
// write to log file
$fp1 = fopen($data_file, 'w') or exit('Data File Error!');
flock($fp1,LOCK_EX);
for ($i = 0; $i < $lines_max; $i++) {
fputs($fp1, $lines[$i]);
}
fclose($fp1);
// write to history file
$fp2 = fopen($history_file, a);
fputs($fp2, $post);
fclose($fp2);
header("Location: " . $script_file); // リロード対策
exit;
} else {
$name = $_COOKIE['lounge_name'];
}
print<<<_HEAD_
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">
<html lang="ja">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta http-equiv="Pragma" content="no-cache" />
<meta http-equiv="Cache-Control" content="no-cache" />
<title>$title</title>
<link rel="stylesheet" href="$css_file" type="text/css" />
</head>
<body>
<div id="container">
_HEAD_;
if (file_exists($data_file)) {
print<<<_FORM_
<form method="post" action="$script_file">
name: <input type="text" name="$name_field" value="$name" size="20" />
$add_link_text<br />
<textarea name="$text_field" cols="80" rows="3"></textarea><br />
<input type="submit" value="送信 / リロード" />
</form>
<hr />
_FORM_;
@include $data_file;
} else {
print '<br /><br /><p>' . $lock_message . '</p><br /><br />';
@include $data_file_2;
}
?>
</div>
</body>
</html>
補助スクリプト
この補助スクリプトを同じディレクトリに置く必要があります。やってることは、クッキーの内容の変更のみです。このクッキーは有効期限の設定がなく、ブラウザを閉じると無効になります。
<?php
$script_file = 'lounge.php';
$add = $_COOKIE['lounge_add'];
if ($add) { $add = 0; }
else { $add = 1; }
setcookie("lounge_add", $add);
header("Location: " . $script_file); // ジャンプ
?>
スタイルシート
スタイルシートの内容はご自由に。これはサンプル。
body {font-family: Trebuchet MS,Arial,Verdana,Helvetica,sans-serif; margin: 30px; text-align: center;} div#container {text-align: left; width: 520px; margin: 10px auto;} textarea {width: 500px; height: 6em; margin: 6px auto;} p {margin: 6px 0; width: 500px; line-height: 130%; padding-bottom: 2px; border-bottom: dotted 1px #b0b0b0;} hr {border: 0; width: 500px; height: 1px; color: #b0b0b0; background-color: #b0b0b0; margin-bottom: 14px; margin-left: 0px;} .ownername {color: darkslategray;} .badname {color: crimson;} .name {color: mediumblue;} .date {font-size: 80%; color: #aaaaaa;}
2007/02/07 ― JST で書き出す部分を単純化。
2007/05/18 ― ヘッダが valid でなかったのを修正。
2007/10/10 ― 投稿内容のリンク判定を強化
(2006/12/19 - 2007/10/10)