すみません、えらく長いこと放置してしまいました。
実はうちの掲示板の機能のうち「スレッド順表示」のことをきれいさっぱり 忘れておりまして、次は削除機能かなあ、 削除機能はなくても掲示板として成立するから少々遅くなってもいいかなあ、 とか考えたっきり放置してしまいました。
でも「スレッド順インデックス」の機能は、 議論向けの掲示板※1では必須の、 重要な機能ですよね。 データ構造にも影響を与えますし。
まあ、プログラム的には、投稿を再帰的にほじって表示していくだけで、 さほど難しいものではないです。
なお、今回紹介するソースは、リスト表示の際と同様、 削除機能実装前の古いバージョンです。
「スレッド順インデックス」は、うちの掲示板の上部のメニューから、 「[スレッド順インデックス]」をクリックすると表示されます。
スレッドの先頭の投稿から、それに対する返信をツリー上に表示します。 1ページあたり表示されるのは、デフォルトでは、 boardテーブルのdefaultrangethreadフィールドで指定されている数だけの スレッドです。 つまり、defaultrangethreadが20に設定されていたら、 激論が激論を呼び延々と続いたスレッドであっても、 最初の投稿だけで終わったスレッドであっても平等に「20スレッド」表示しますから、 1画面で表示される投稿数は一定しません。 かつてのfj.comp.lang.cのmalloc and freeみたいな長大なスレッドができる場合には この仕様ではまずいかもしれません(わかる人にしかわからん話ですみません)。
スレッド順インデックスでは、一覧表示と同様、 以下のようにURLからパラメタを渡すことで、 表示する発言の範囲を指定することができます。
http://kmaebashi.com/bbs/thread.php?boardid=kmaebashibbs&from=10&range=3
ここで、fromは起点となるスレッドトップの投稿の番号、 rangeは一度に表示するスレッドの数です。 fromに10を指定したが10番の投稿がスレッドのトップでない場合、 10以下で最大のスレッドトップの投稿が起点となります。
また、スレッド順インデックスにおいて、 先頭の「▼」をクリックすると、 そのスレッドに含まれる投稿が日付順に一覧表示されます。 この表示機能は前回説明したlist.phpで実現されています。
というわけで次はソースです。
1: <?php 2: require 'connect_db.php'; 3: require 'util.php'; 4: 5: function show_child($board_id, $parent) { 6: $sql_str = sprintf("select * from message where boardid='%s'" 7: . "and parent = %d", 8: $board_id, $parent); 9: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str); 10: echo '<ul>'; 11: while ($row = mysql_fetch_assoc($result)) { 12: echo '<li>'; 13: include 'threaditem.php'; 14: show_child($board_id, $row["serialid"]); 15: } 16: echo '</ul>'; 17: } 18: 19: 20: ?> 21: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"> 22: <html lang="ja-JP"> 23: <head> 24: <meta http-equiv="Content-Type" content="text/html; charset=EUC-JP"> 25: <link rel="STYLESHEET" TITLE="default" TYPE="text/css" href="./bbs.css"> 26: <?php 27: if (!isset($_GET["boardid"])) { 28: die('URLが変です。'); 29: } 30: $board_id=$_GET["boardid"]; 31: $sql_str = sprintf("select * from board where boardid='%s'", 32: $board_id); 33: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str); 34: if (mysql_num_rows($result) != 1) { 35: die("掲示板のIDが変です。"); 36: } 37: $row = mysql_fetch_assoc($result); 38: $board_name=$row["name"]; 39: $homepage=$row["homepage"]; 40: $defaultrangethread=$row["defaultrangethread"]; 41: if (!isset($_GET["from"])) { 42: $fromfirst=true; 43: } else { 44: $from = $_GET["from"]; 45: if (!ctype_digit($from)) { 46: die("fromが変です。"); 47: } 48: $fromfirst=false; 49: } 50: if (!isset($_GET["range"])) { 51: $range = $defaultrangethread; 52: } else { 53: $range = $_GET["range"]; 54: if (!is_positive_number($range)) { 55: die("rangeが変です。"); 56: } 57: } 58: $sql_str = sprintf("select * from message where boardid='%s' and parent is null ", 59: $board_id); 60: if (!$fromfirst) { 61: $sql_str .= sprintf("and serialid <= %d ", $from); 62: } 63: $sql_str .= sprintf("order by serialid desc limit 0, %d", 64: $range); 65: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str); 66: ?> 67: <TITLE><?=$board_name?> スレッド表示</TITLE> 68: </head> 69: <body> 70: <div style=" 71: text-align:center; 72: color: white; 73: font-size: large; 74: background: #0000ff; 75: padding-top: 2px; 76: border-top: #ccccff 2px solid; 77: border-bottom: #000099 2px solid; 78: padding-bottom: 2px; 79: padding-left: 1em; 80: border-left: #ccccff 2px solid; 81: border-right: #000099 2px solid; 82: padding-right: 2px;"> 83: <?=$board_name?> 84: </div > 85: <BR><BR> 86: [<a href="./list.php?boardid=<?=$board_id?>">日付順表示</a>] 87: [<a href="./list.php?boardid=<?=$board_id?>&mode=index">日付順インデックス</a>] 88: [<a href="./thread.php?boardid=<?=$board_id?>">スレッド順インデックス</a>]<br> 89: <br> 90: <hr> 91: <center> 92: <a href="./form.php?boardid=<?=$board_id?>">新規投稿</a> | 93: <a href="<?=$homepage?>">開設者ホームページへ戻る</a> | 94: <a href="http://kmaebashi.com/bbshelp.html">ヘルプ</a> 95: </center> 96: <hr> 97: <ul> 98: <?php 99: while ($row = mysql_fetch_assoc($result)) { 100: if (!isset($firstid)) { 101: $firstid = $row["serialid"]; 102: } 103: $lastid = $row["serialid"]; 104: ?> 105: <li> 106: <a href="./list.php?boardid=<?=$board_id?>&thread=<?=$row["serialid"]?>"> 107: ▼</a> 108: <?php 109: include 'threaditem.php'; 110: show_child($board_id, $row["serialid"]); 111: ?> 112: </li> 113: <?php 114: } 115: ?> 116: </ul> 117: <hr> 118: <div align="center"> 119: <?php 120: $sql_str = sprintf("select max(serialid) from message where boardid='%s'" 121: . "and parent is null", 122: $board_id); 123: $result = mysql_query($sql_str) or die('SQLエラー'.$sql_str); 124: $row = mysql_fetch_row($result); 125: $max = $row[0]; 126: if ($max > $firstid) { 127: $prevlink = sprintf("./thread.php?boardid=%s&from=%d&range=%d", 128: $board_id, $from + $range, $range); 129: ?> 130: [<a href="<?=$prevlink?>"> 131: より新しい投稿</a>] 132: <?php 133: } 134: if ($lastid > 0) { 135: $nextlink = sprintf("./thread.php?boardid=%s&from=%d&range=%d", 136: $board_id, $lastid-1, $range); 137: ?> 138: [<a href="<?=$nextlink?>"> 139: より古い投稿</a>] 140: <?php 141: } 142: ?> 143: </body>
5〜17行目で、show_child()という関数が定義されており、 スレッド順表示においてはこれがキモなんですが、ひとまず飛ばします。
その後、57行目あたりまでは、例のごとく入力チェックを行っています。
58行目から生成するSQL文で、fromで指定された番号以降の、 rangeで指定された数だけの、スレッドのトップの投稿を検索しています。 スレッドのトップの投稿を抽出するために、 「parents is null」という条件が追加されています。
99行目から始まるwhileループで、これを順に表示しています。
表示自体は、threaditem.phpというファイルをincludeしています(109行)。 その中身は以下のとおり。
1: <?php 2: $serialid=$row["serialid"]; 3: $subject = htmlspecialchars($row["subject"]); 4: $name = htmlspecialchars($row["name"]); 5: $date = format_date($row["posteddate"]); 6: ?> 7: [<?=$serialid?>] 8: <a href="./list.php?boardid=<?=$board_id?>&from=<?=$serialid?>&range=1"> 9: <?=$subject?> 10: </a> 11: 。。<?=$name?> 12: 。。<?=$date?>
さて、ここまででは、スレッドのトップの投稿がずらずらとリスト表示されるだけですが、 110行目でさっきの関数show_child()を呼び出しているのがキモです。
show_child()は、板のIDと、親の投稿のserialidを引数として受け取り、 受け取った親に対する直接の返信である投稿を、 6〜8行目で生成しているSQLで抽出しています。 あとは、その投稿をwhileループで順に表示しているだけですが、 その際14行目でshow_child()自身を呼び出しています。
これが再帰呼び出しです。 再帰呼び出しのなんたるかをここで説明するのはちと荷が重いので、 わからない人は、各種参考書やらWebページやらを当たってみてください。
ところで、再帰呼び出しのキモは、 「その関数のローカル変数は、関数の呼び出しごとに独立した値を持つ」 ってことだと私は思ってます。
というわけで、再帰の説明に階乗を使うのはいかがなものか、と思ってます。 あんなもんループで書いたほうがずっと簡単でわかりやすいわけで。
このページに対してご意見・ご質問・ご感想等をいただいた場合、 公開することがあります。