このページを書くのに先立って、 本番稼動中の掲示板のスクリプトに削除機能を実装しました。 削除機能を付けると一覧表示のスクリプトにも結構手が入るので、 どうせならスクリプト修正後にこのページを書いた方がよいような気がしたからです。
---が、いざスクリプトを修正してみると、 一覧表示部分のコードに予想以上にごちゃごちゃした修正が入ってしまったので、 このページを参考にして掲示板を作ってみよう、 なんて人(いるのかしらん)にとってはかえってわかりにくいんじゃないか、 と思えてきました。
というわけで今回説明するコードは、削除機能実装前の古いソースです ※1。 現在うちの本番系掲示板で稼動しているものとは異なりますので注意してください。 まあ、何事にも無計画な奴がやってることだということで…(^^;;
うちの掲示板の上部のメニューを見ると、以下の3つの表示形式があります。
このうち、スレッド順インデックスは見栄えが明らかに異なりますが、 日付順表示、日付順インデックスに関しては、割と似たような表示形式になっています。 異なるのは、メッセージそのものが表示されるのか、 タイトル・投稿者・日付だけが表示されるのか、という点だけです。 「より古い投稿」、「より新しい投稿」のリンクなども、 一度に表示される件数が異なることを除き、同じ動きをします。
そこで、このふたつの表示形式については、ひとつのプログラムを共用することにしました。
以下のようにURLで日付順表示であるか日付順インデックスであるかを指定しています。 modeはplain(日付順表示)またはindex(日付順インデックス)で、 省略時はplainと解釈されます。
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&mode=index
プログラムの構造は、以下のように、list.phpというスクリプトが、 plain.phpまたはindex.phpをincludeしています。
plain.phpとindex.phpは、それぞれひとつの投稿分を表示します。 list.php内で、表示する発言数分ループしながらincludeしています。
また、この掲示板には、スレッド順インデックスの画面で 「▼」をクリックすることで、そのスレッドの発言だけを一覧表示するという 機能がありますが、この表示もlist.phpで行っています。 以下のようにURLでスレッドのトップの発言のIDを与えています。
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&thread=0
plain/indexのモードと、「スレッド一覧表示」のモードは直交していますので、 以下のようなURLを与えることで、スレッド一覧のインデックス表示も可能です。
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&thread=0&mode=index
一度に表示する発言の数は、rangeというパラメータで制御しています。 以下のURLを渡すと、一度に3つの投稿を表示するようになります。
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&range=3
日付順インデックス、スレッド順インデックスから投稿のタイトルをクリックすると その投稿がひとつだけ表示されますが、これはrangeを1にすることで実現しています。
既にいくつか説明しましたが、list.phpにはGETで渡すパラメタが多いので、 ここで一覧にしておきます。
| 名前 | 意味 | 省略時解釈 |
|---|---|---|
| boardid | 掲示板のIDです。boardテーブルのboardidに対応します。 | 省略不能 |
| mode | plainまたはindexで表示モードを指定します。 | plain |
| thread | これが指定されると、特定のスレッドの一覧表示を行います。 スレッドのトップの発言のIDを指定します。 | 通常の一覧表示 |
| from | 日付順表示、日付順インデックスにおいて、先頭に表示される発言のIDです。 | 最新の発言。 |
| offset | スレッド一覧表示にて、先頭に表示される発言の、 そのスレッド内での位置を示します。 offsetが5のとき、そのスレッド内で6番目の発言が先頭に表示されます (offsetはゼロオリジン)。 | スレッド中の最古の発言。 |
| range | ページあたりの表示件数。 | boardテーブルから取得する。 |
まずはlist.phpです。結構長いです。
1: <?php
2: require 'connect_db.php';
3: require 'util.php';
4: ?>
5: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
6: <html lang="ja-JP">
7: <head>
8: <meta http-equiv="Content-Type" content="text/html; charset=EUC-JP">
9: <link rel="STYLESHEET" TITLE="default" TYPE="text/css" href="./bbs.css">
10: <?php
11: if (!isset($_GET["boardid"])) {
12: die('URLが変です。');
13: }
14: if (!isset($_GET["mode"])) {
15: $mode = 'plain';
16: } else if ($_GET["mode"] == 'index') {
17: $mode = 'index';
18: } else if ($_GET["mode"] == 'plain') {
19: $mode = 'plain';
20: } else {
21: die("modeが変です。");
22: }
23: if (isset($_GET["thread"])) {
24: $thread = $_GET["thread"];
25: if (!ctype_digit($thread)) {
26: die("threadが変です。");
27: }
28: }
29: $board_id=$_GET["boardid"];
30: $sql_str = sprintf("select * from board where boardid='%s'",
31: $board_id);
32: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str);
33: if (mysql_num_rows($result) != 1) {
34: die("掲示板のIDが変です。");
35: }
36: $row = mysql_fetch_assoc($result);
37: $board_name=$row["name"];
38: $homepage=$row["homepage"];
39: $defaultrange=$row["defaultrange"];
40: $defaultrangeindex=$row["defaultrangeindex"];
41:
42: if (!isset($thread)) {
43: # 通常表示ではfromを使う
44: if (isset($_GET["from"])) {
45: $from = $_GET["from"];
46: if (!ctype_digit($from)) {
47: die("fromが変です。");
48: }
49: }
50: } else {
51: # スレッド一覧表示ではoffsetを使う
52: if (isset($_GET["offset"])) {
53: $offset = $_GET["offset"];
54: if (!ctype_digit($offset)) {
55: die("offsetが変です。");
56: }
57: }
58: }
59: if (!isset($_GET["range"])) {
60: if ($mode == 'plain') {
61: $range = $defaultrange;
62: } else if ($mode == 'index') {
63: $range = $defaultrangeindex;
64: }
65: } else {
66: $range = $_GET["range"];
67: if (!is_positive_number($range)) {
68: die("rangeが変です。");
69: }
70: }
71: $sql_str = sprintf("select * from message where boardid='%s' ",
72: $board_id);
73: if (isset($from)) {
74: $sql_str .= sprintf("and serialid <= %d ", $from);
75: }
76: if (!isset($thread)) {
77: $sql_str .= sprintf("order by serialid desc limit %d", $range);
78: } else {
79: $sql_str .= sprintf(" and top = %d order by serialid ", $thread);
80: if (isset($offset)) {
81: $sql_str .= sprintf("limit %d, %d", $offset, $range);
82: } else {
83: $sql_str .= sprintf("limit 0, %d", $range);
84: }
85: }
86: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str);
87: ?>
88: <TITLE><?=$board_name?> 一覧表示</TITLE>
89: </head>
90: <body>
91: <div style="
92: text-align:center;
93: color: white;
94: font-size: large;
95: background: #0000ff;
96: padding-top: 2px;
97: border-top: #ccccff 2px solid;
98: border-bottom: #000099 2px solid;
99: padding-bottom: 2px;
100: padding-left: 1em;
101: border-left: #ccccff 2px solid;
102: border-right: #000099 2px solid;
103: padding-right: 2px;">
104: <?=$board_name?>
105: </div >
106: <BR><BR>
107: [<a href="./list.php?boardid=<?=$board_id?>">日付順表示</a>]
108: [<a href="./list.php?boardid=<?=$board_id?>&mode=index">日付順インデックス</a>]
109: [<a href="./thread.php?boardid=<?=$board_id?>">スレッド順インデックス</a>]<br>
110: <br>
111: <hr>
112: <center>
113: <a href="./form.php?boardid=<?=$board_id?>">新規投稿</a> |
114: <a href="<?=$homepage?>">開設者ホームページへ戻る</a> |
115: <a href="http://kmaebashi.com/bbshelp.html">ヘルプ</a>
116: </center>
117: <hr>
118:
119: <?php
120: if ($mode == 'index') {
121: echo "<table>";
122: }
123: ?>
124: <?php
125: while ($row = mysql_fetch_assoc($result)) {
126: $serialid=$row["serialid"];
127: $subject = htmlspecialchars($row["subject"]);
128: $name = htmlspecialchars($row["name"]);
129: $url = htmlspecialchars($row["url"]);
130: $message = htmlspecialchars($row["message"]);
131: $top = $row["top"];
132: $date = format_date($row["posteddate"]);
133:
134: if (!isset($firstid)) {
135: $firstid = $serialid;
136: }
137: $lastid = $serialid;
138:
139: if ($mode == 'plain') {
140: include 'plain.php';
141: } else if ($mode == 'index') {
142: include 'index.php';
143: }
144: }
145: if ($mode == 'index') {
146: echo "</table>";
147: }
148: if (!isset($firstid)) {
149: die("該当するレスなし。");
150: }
151: ?>
152: <hr>
153: <div align="center">
154: <?php
155: $sql_str = sprintf("select min(serialid), max(serialid) "
156: . "from message where boardid='%s' ",
157: $board_id);
158: if (isset($thread)) {
159: $sql_str .= sprintf("and top = %d", $thread);
160: }
161: $result = mysql_query_or_die($sql_str);
162: $row = mysql_fetch_row($result);
163: $db_min = $row[0];
164: $db_max = $row[1];
165:
166: if (!isset($thread)) {
167: # 通常表示
168: if ($db_max > $firstid) {
169: $prevlink = sprintf("./list.php?boardid=%s&from=%d&range=%d&mode=%s",
170: $board_id, $firstid + $range, $range, $mode);
171: $prevmessage = "より新しい投稿";
172: }
173: } else {
174: # スレッド一覧
175: if (isset($offset) && $offset > 0) {
176: $prevlink = sprintf("./list.php?boardid=%s&range=%d&mode=%s"
177: . "&thread=%d",
178: $board_id, $range, $mode, $thread);
179: $new_offset = $offset - $range;
180: if ($new_offset > 0) {
181: $prevlink .= sprintf("&offset=%d", $new_offset);
182: }
183: $prevmessage = "より古い投稿";
184: }
185: }
186: if (isset($prevlink)) {
187: ?>
188: [<a href="<?=$prevlink?>">
189: <?=$prevmessage?></a>]
190: <?php
191: }
192: if (!isset($thread)) {
193: # 通常表示
194: if ($db_min < $lastid) {
195: $nextlink = sprintf("./list.php?boardid=%s&from=%d&range=%d&mode=%s",
196: $board_id, $lastid - 1, $range, $mode);
197: $nextmessage = "より古い投稿";
198: }
199: } else {
200: # スレッド一覧
201: if ($db_max > $lastid) {
202: if (isset($offset)) {
203: $new_offset = $offset + $range;
204: } else {
205: $new_offset = $range;
206: }
207: $nextlink = sprintf("./list.php?boardid=%s&range=%d&mode=%s"
208: . "&thread=%d&offset=%d",
209: $board_id, $range, $mode, $thread, $new_offset);
210: $nextmessage = "より新しい投稿";
211: }
212: }
213: if (isset($nextlink)) {
214: ?>
215: [<a href="<?=$nextlink?>">
216: <?=$nextmessage?></a>]
217: <?php
218: }
219: ?>
220: </div>
221: </body>
222: </html>
70行目くらいまでは、ひたすらパラメタの取り込みとチェックを行っています。
71行目から、SQL文の組み立てに入っています。 たとえば
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&from=20&range=5
というURLは、「IDが20の投稿を起点に投稿を5つ表示する」という意味ですが、 そのSQLは以下のように組み立てられます(適宜改行を入れています)。
select * from message where boardid='kmaebashibbs' and serialid <= 20
order by serialid desc limit 5
特定のスレッドの一覧表示モードでは、たとえば以下のようなURLに対し、
http://kmaebashi.com/bbs/list.php?boardid=kmaebashibbs&range=3&mode=plain&thread=2&offset=3
以下のようなSQLが組み立てられます。
select * from message where boardid='kmaebashibbs'
and top = 2 order by serialid limit 3, 3
こうして得られた検索結果について、125行目からのループで表示しています。 表示の際は、日付順表示の場合はplain.phpを、 日付順インデックスの場合はindex.phpをそれぞれincludeしています(140, 142行目)。
「より新しい投稿」「より古い投稿」のリンクの作成は、 日付順表示および日付順インデックスの場合と、スレッド一覧表示の場合とでは 大幅に異なります(その意味で、このソースまで統合したのは失敗だったかも)。 異なる点としては、以下のようなものがあります。
というわけで、実質この部分のコードはif文で完全に分離されています(166〜219行目)。
なお、スレッド一覧の際に offset指定(SQLレベルではlimit n, mという形式の指定)を使っているのは、 スレッド一覧では表示される投稿のIDが飛び飛びであるため IDによる指定ができないためです。
表示の際にincludeしているファイルの内容は、それぞれ以下の通りです。 まずはplain.phpから。
1: <DIV class="res" style="background-color:white;"> 2: <DIV style="margin: 10px 10px 10px 10px;"> 3: <BR> 4: <DIV style="line-height:0%;"> 5: [<?=$serialid?>] 6: <STRONG><font size="4"> 7: <?= $subject ?> 8: </font></STRONG> 9: <DIV align="right"> 10: <a href="./form.php?boardid=<?=$board_id?>&parent=<?=$serialid?>">返信</a></DIV> 11: </DIV> 12: <BR> 13: <BR> 14: <DIV style="line-height:0%;"> 15: 投稿者:<?= $name ?> 16: <DIV align="right"><?=$date?></DIV> 17: </DIV> 18: <BR> 19: Link:<a href="<?=$url?>"><?=$url?></a> 20: <HR> 21: <PRE> 22: <?php 23: $message2 = convert_message($message); 24: ?> 25: <?=$message2?> 26: </PRE> 27: </DIV> 28: </DIV> 29: <div align="center"> 30: [<a href="./thread.php?boardid=<?=$board_id?>&from=<?=$top?>&range=1"> 31: このレスを含むスレッドを表示</a>] 32: </div> 33: <br><br>
次はindex.phpです。
1: <tr> 2: <td align="right">[<?=$serialid?>]</td> 3: <td> 4: <a href="./list.php?boardid=<?=$board_id?>&from=<?=$serialid?>&range=1"> 5: <?=$subject?> 6: </a> 7: </td> 8: <td><?=$name?></td> 9: <td><?=$date?></td> 10: </tr>
それぞれ、list.phpで設定された変数を元に、ひとつの投稿分を表示しています。
index.phpは、全体が<table>で囲まれることを前提にしています。 そのため、list.phpにて、indexモードの時に限り<table>, </table>を表示するようにしています(121,146行目)。
このページに対してご意見・ご質問・ご感想等をいただいた場合、 公開することがあります。