仕様など

うちのサイトの「ラウンジ」で使うために作成した PHP スクリプトの紹介です。要するにごくシンプルな掲示板です。こんなスクリプトでさえ、参考書と首っ引きで、ウェブで探した情報にも助けられて、やっと書けるような者が作成したものなので、あまりあてになりませんが、自由に使ってください(ただし、問題が起きても作者免責でよろしく)。まずいところがあったら、ご指摘よろしくお願いします。

最近はメールだけでなく、掲示板にもスパムがくるようなので、スパム防衛のあたりも考慮して、仕様を考えました。原始的な方法でスパムよけをしています。また、主義として「できるだけシンプル」なものになっています。

スパムがくる原因として、スクリプト配布元が表示を義務づけている配布元へのリンクがあるようです。検索エンジンを使えば、リンクの文字列から同内容の攻撃が通用するスクリプトがまとめて検索できてしまうわけですから。スクリプト配布元へのリンクは別のリンクページなどにして、掲示板そのものにはスクリプト作者の著作権表示だけにする、というような方式のほうがよいのでは。

動作には、スクリプト本体 (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\">&lt;&lt; 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"$nametime() + 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_filea);
    
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" />
 &nbsp; 
$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)