【 掲示板 】

1. ログファイルフォーマット
2. 画面構成
3. 設定ファイル
4. ロック処理
5. メインスクリプト
6. 表示用スクリプト
7. 共通処理
8. ログファイルの処理

再利用を見越した設計
  • カレントのログファイル、過去ログファイル共に ODBC 経由のDB として
    アクセスする可能性や、Excel を使った加工、他のアプリケーションへの
    データの転用を考えて、カンマで区切られた CSV フォーマットとします

  • DB のテーブルとして考えた時に、列名定義として常に1行目はタイトル
    とします

  • 列の定義は、掲示板の最低ラインの機能のみを満たす4種類とします


  • タイムスタンプ,ユーザ名,タイトル,本文
    2003-12-30(Tue) 08:19:34,lightbox,サンプルタイトル,サンプル本文1行目<BR>2行目
    


  • フィールドの区切り文字をカンマとする為に、投稿データ内のカンマは、&#44; に置き換えます
  •   ソース名 内容
    1
    head.php
    HTML として必要なヘッダ部分と、アプリとしての
    ヘッダ部分
    有効とするかどうか
    2
    input.php
    入力部分
    3
    -------
    ログデータ表示
    4
    foot.php
    各ページへのリンク及びアプリとしてのフッターと
    デバッグ表示


  • ログデータは埋め込み表示が無理なので、print 文で $LINE グローバル変数を出力します

  • ログ読み出し関数では、 $LINE グローバル変数を編集する事になります


  • ini 形式の使用
  • セクションは用いませんが、KEY=値 形式のデータを、拡張子 ini の
    ファイルに用意します

  • 値に "=" が含まれる事を禁じます

  • スクリプト実行開始時に、$_GET スーパーグローバルにセットします


  • 空行と、行の先頭に ";" のある行は無視されます。これは、一般的な仕様です

  • ; ログファイルの最大行、この値を超えると過去ログへと移動
    INI_MAXROW=20
     
    ; ページ毎の行数
    INI_ROWPERPAGE=5
     
    ; ログファイル名
    INI_LOGFILE=logfile.csv
     
    ; 過去ログ1ファイルの最大件数
    INI_PASTMAXROW=20
     
    ; LOCK 関数のリトライ回数
    INI_LOCKCNT=3
     
    ; LOCK 用ディレクトリ名
    INI_LOCKDIR=lock
     
    ; タイトル文字列
    INI_TITLE=タイムスタンプ,ユーザ名,タイトル,本文
    


  • ファイルシステムでロックしようとする場合、「作成処理」が成功するかどうかでロックの権利を得るのが
    最も簡単です。

  • 通常 perl で使用されている方法をそのまま使用します

  • ※ ロックの解除には、rmdir を使用します
  • # ***********************************************************
    # ロック関数
    # ***********************************************************
    function lockbydir( ) {
     
    	$lock = $_GET['INI_LOCKDIR'];
    	$lockcnt = $_GET['INI_LOCKCNT'];
     
    	$ret = true;
     
    	if ( file_exists( $lock ) ) {
    		$astamp = stat($lock);
    		$laststamp = $astamp[9];
    		if ( $laststamp < time() - 60 ) {
    			rmdir( $lock );		# 古いディレクトリの削除
    		}
    	}
     
    	$cnt = 0;
     
    	while( 1 ) {
    		if ( mkdir( $lock ) ) {
    			break;	# 成功
    		}
    		$cnt++;
    		if ( $cnt > $lockcnt ) {
    			$ret = false;	# 失敗
    			break;
    		}
    		sleep(1);
    	}
     
    	return $ret;
     
    }
    

    bbs.php
  • アプリケーションの全体を見渡せるようにします。全て関数にするのも考
    えもので、アプリケーション固有か、共通部分かを考え、さらにメンテナン
    ス性を考慮して配置します


  • <?
    require_once( "global.php" );		# 共通処理
    require_once( "function.php" );		# 関数
    # **********************************************************
    # 掲示板
    # **********************************************************
    $LIST		= "";	# データ表示用
    $PAGE_LINK	= "";	# ページリンク用 ( foot.php で埋め込み )
    $USER_NAME	= "";
    $DEBUG		= true;
     
    if ( $_COOKIE['UserName'] != "" ) {
    	$USER_NAME = $_COOKIE['UserName'];
    }
     
    # **********************************************************
    # ログ書き込み
    # **********************************************************
    if ( $_SERVER['REQUEST_METHOD'] == "POST" ) {
     
    	setcookie( "UserName", $_POST['UserName'], time()+60*60*24*30 );
    	$USER_NAME = $_POST['UserName'];
     
    	if ( lockbydir( ) ) {
    		WriteLog();
    		rmdir($_GET['INI_LOCKDIR']);
     
    		if ( !$DEBUG ) {
    			print "<SCRIPT language='JavaScript'>";
    			print "top.location = '{$_SERVER['SCRIPT_NAME']}'";
    			print "</SCRIPT>";
    			exit();
    		}
    	}
    	else {
    		print "他で使用中です";
    	}
    }
     
    # **********************************************************
    # ログ読み込み
    # **********************************************************
    LoadLog( );
     
    # **********************************************************
    # 表示 ( VIEW )
    # **********************************************************
    require_once( "head.php" );
    require_once( "input.php" );
     
    print $LIST;	# データ表示
     
    require_once( "foot.php" );
    ?>
    

  • head.php
  • <HTML>
    <HEAD>
    <META HTTP-EQUIV="Content-Type" CONTENT="text/html; CHARSET=shift_jis">
    </HEAD>
    <BODY>
    


  • input.php
  • <FORM name="frm" method="post">
    	<TABLE border="0" width=400>
    		<TR>
    			<TD>名前</TD>
    			<TD width=300>
    				<INPUT
    					type="text"
    					name="UserName"
    					value="<?= $USER_NAME ?>"
    				>
    			</TD>
    		</TR>
    		<TR>
    			<TD>タイトル</TD>
    			<TD><INPUT type="text" name="Title"></TD>
    		</TR>
    		<TR>
    			<TD colspan=2>
    				<TEXTAREA name="Message" cols="60" rows="10"></TEXTAREA>
    			</TD>
    		</TR>
    		<TR>
    			<TD colspan=2>
    				<INPUT type=submit name="send" value="登録">
    			</TD>
    		</TR>
    	</TABLE>
    </FORM>
    <HR size="1" color="silver">
    


  • foot.php
  • <?= $PAGE_LINK ?>
    <HR size="1" color="silver">
     
    <?
    	if ( $DEBUG ) {
     
    		print "<PRE>";
    		print_r( $_POST );
    		print "</PRE>";
     
    		print "<PRE>";
    		print_r( $_GET );
    		print "</PRE>";
     
    	}
    ?>
     
    </BODY>
    </HTML>
    

    <?
    # **********************************************************
    # 共通処理
    # **********************************************************
    header( "Content-Type: text/html; Charset=shift_jis" );
    header("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
    error_reporting( E_ALL ^ E_WARNING ^ E_NOTICE );
     
    foreach( $_POST as $Key => $Value ) {
    	$Value = str_replace("\\\\", "\\", $Value );
    	$Value = str_replace( "\\\"", "\"", $Value );
    	$Value = str_replace( "\\'", "'", $Value );
    	$_POST[$Key] = $Value;
    }
     
    $BBS_INI = "bbs.ini";
     
    # **********************************************************
    # 設定ファイル読み込み
    # **********************************************************
     
    	# ページ指定をするパラメータが無い場合は、先頭ページ
    	if ( $_GET['page'] == "" ) {
    		$_GET['page'] = 1;
    	}
     
    	$file = file( $BBS_INI );
    	for( $i = 0; $i < count( $file ); $i++ ) {
     
    		# 改行削除
    		$target = rtrim( $file[$i] );
     
    		# 空行は無視
    		if ( $target == "" ) {
    			continue;
    		}
     
    		# コメント文字
    		if ( substr( $target, 0, 1 ) == ";" ) {
    			continue;
    		}
     
    		# 行を区切り文字で分解
    		$ini = explode( "=", $target );
     
    		$_GET[$ini[0]] = $ini[1];
     
    	}
     
     
    ?>
    

    カレントログファイル
  • カレントのログファイルは、最大件数の設定数 + 1 のレコードを持つ

  • 制限を超えると、過去ログファイルへ最も古い( 最後の行 ) レコード
    が移動される


  • 過去ログファイル
  • 過去ログファイルは、最大件数の設定数 + 1 のレコードを持つ

  • 制限を超えると、その過去ログファイルは使用されずに日付・時刻
    によるファイル名を持つ新しいファイルが作成されてそちらに書き込
    まれる


  • <?
    # ***********************************************************
    # 対象ログデータ読み取り
    # ***********************************************************
    function GetTargetLogArray() {
     
    	# ログファイルを配列として読み込み
    	$file = file( $_GET['INI_LOGFILE'] );
     
    	# 一行目のデータはタイトルデータなので排除する
    	array_shift( $file );
     
    	return $file;
     
    }
     
    # ***********************************************************
    # ログ読み取り
    # ***********************************************************
    function LoadLog( ) {
     
    	global $LIST,$PAGE_LINK;
     
    	# ログファイルが存在しない場合はなにもしない
    	if ( !file_exists( $_GET['INI_LOGFILE'] ) ) {
    		return;
    	}
     
    	# ログデータを配列として読み込み
    	$file = GetTargetLogArray();
     
    	$pagecnt = 0;
    	$row_par_page = $_GET['INI_ROWPERPAGE'];
     
    	for( $i = 0; $i < count( $file ); $i++ ) {
     
    		# ページリンクの作成
    		if ( ( $i % $row_par_page ) == 0 ) {
    			$pagecnt++;
     
    			# 自ページの場合
    			if ( $pagecnt == $_GET['page'] ) {
    				$PAGE_LINK .= "$pagecnt&nbsp;&nbsp;";
    			}
    			# 他ページの場合
    			else {
    				$PAGE_LINK .= "<A href" . "='{$_SERVER['SCRIPT_NAME']}";
    				$PAGE_LINK .= "?page=$pagecnt'>$pagecnt</A>&nbsp;&nbsp;";
    			}
    		}
     
    		# ログの表示データ作成 ( 自ページ分のみ )
    		if ( (int)($i / $row_par_page) + 1 == $_GET['page'] ) {
    			# 行を区切り文字で分解
    			$row = explode( ",", $file[$i] );
     
    			# 表示データの作成
    			$LIST .= "$row[0] $row[1] $row[2]<BR>";
    			$LIST .= "$row[3]<BR>";
    			$LIST .= "<HR size=1 color=silver>";
    		}
     
    	}
     
    }
     
    # ***********************************************************
    # タイトルデータのみのログデータの作成
    # ***********************************************************
    function CreateBaseLog() {
     
    	$fp = fopen( $_GET['INI_LOGFILE'], "w" );
    	fputs( $fp, $_GET['INI_TITLE'] ."\n" );
    	fclose( $fp );
     
    }
     
    # ***********************************************************
    # POST されたデータをログフォーマットに編集
    # ***********************************************************
    function CreateCurrentLogText() {
     
    	# 日付フォーマット
    	$Date		= date("Y-m-d(D) H:i:s");
     
    	# カンマのコンバート
    	$UserName	= str_replace( ",", "&#44;", $_POST['UserName'] );
    	$Title		= str_replace( ",", "&#44;", $_POST['Title'] );;
    	$Message	= str_replace( ",", "&#44;", $_POST['Message'] );
     
    	# その他のコンバート
    	$Message	= str_replace( "<", "&lt;", $Message );
    	$Message	= str_replace( ">", "&gt;", $Message );
    	$UserName	= str_replace( "<", "&lt;", $UserName );
    	$UserName	= str_replace( ">", "&gt;", $UserName );
    	$Title		= str_replace( "<", "&lt;", $Title );
    	$Title		= str_replace( ">", "&gt;", $Title );
     
    	# 改行のコンバート
    	$Message	= str_replace( "\r", "", $Message );
    	$Message	= str_replace( "\n", "<BR>", $Message );
     
    	return "$Date,$UserName,$Title,$Message\n";
     
    }
     
    # ***********************************************************
    # 配列を全てファイルへ書き込み
    # ***********************************************************
    function WriteAllArray( &$fp, &$Array ) {
     
    	for( $i = 0; $i < count($Array); $i++ ) {
    		fputs( $fp, $Array[$i] );
    	}
     
    }
     
    # ***********************************************************
    # ログ書き込み
    # ***********************************************************
    function WriteLog( ) {
     
    	# ログファイルが存在しない場合は作成
    	if ( !file_exists( $_GET['INI_LOGFILE'] ) ) {
    		CreateBaseLog();
    	}
     
    	# ログデータを配列として読み込み
    	$file = GetTargetLogArray();
     
    	# ログファイルを書き込みモードでオープン
    	$fp = fopen( $_GET['INI_LOGFILE'], "w" );
     
    	# タイトルの書き込み
    	fputs( $fp, $_GET['INI_TITLE'] ."\n" );
    	# POST されたデータの書き込み
    	fputs( $fp, CreateCurrentLogText() );
     
    	# ログデータの書き込み
    	if ( count($file) >= $_GET['INI_MAXROW'] ) {
    		PastLog( array_pop( $file ) );
    		WriteAllArray( $fp, $file );
    	}
    	else {
    		WriteAllArray( $fp, $file );
    	}
    	fclose( $fp );
     
    }
     
    # ***********************************************************
    # 過去ログ書き込み
    # ***********************************************************
    function PastLog( $PastRow ) {
     
    	# 過去ログディレクトリが存在しない場合は作成
    	if ( !file_exists( "past" ) ) {
    		mkdir( "past" );
    	}
     
    	# 過去ログディレクトリを OPEN
    	$dir = opendir( "past" );
     
    	# 過去ログディレクトリを 読み込み
    	$arr_file = array();
    	$count = 0;
    	while( false !== ($filename = readdir($dir)) ) {
    		if ( $filename != '.' && $filename != '..' ) {
    			$arr_file[$count] = $filename;
    			$count++;
    		}
    	}
     
    	# ディレクトリ内にファイルがあった時
    	if ( $arr_file ) {
    		# 最も新しい過去ログファイル名を取得
    		rsort( $arr_file );
    		$target = "past\\" . $arr_file[0];
    	}
    	# ディレクトリ内にファイルが無かった時
    	else {
    		# 現在の日付・時刻でファイル名を作成
    		$target = date("YmdHis") . ".log";
    		$target = "past\\" . $target;
    		$fp = fopen( $target, "w" );
    		# 書き込み
    		fputs( $fp, $_GET['INI_TITLE'] ."\n" );
    		fclose( $fp );
    	}
     
    	# ログファイルを配列として読み込み
    	$file = file( $target );
     
    	# ログデータの書き込み
    	if ( count($file) >= $_GET['INI_PASTMAXROW'] + 1 ) {
     
    		# 新しい過去ログファイルを作成して書き込む
    		$target = date("YmdHis") . ".log";
    		$target = "past\\" . $target;
    		$fp = fopen( $target, "w" );
    		# 書き込み
    		fputs( $fp, $_GET['INI_TITLE'] ."\n" );
    		fputs( $fp, $PastRow );
     
    	}
    	else {
    		# ログファイルを書き込みモードでオープン
    		$fp = fopen( $target, "w" );
     
    		# タイトルの書き込み
    		fputs( $fp, $_GET['INI_TITLE'] ."\n" );
     
    		# POST されたデータの書き込み
    		fputs( $fp, $PastRow );
     
    		array_shift( $file );
    		WriteAllArray( $fp, $file );
     
    	}
     
    	fclose( $fp );
     
    }
     
    # ***********************************************************
    # ロック関数
    # ***********************************************************
    function lockbydir( ) {
     
    	$lock = $_GET['INI_LOCKDIR'];
    	$lockcnt = $_GET['INI_LOCKCNT'];
     
    	$ret = true;
     
    	if ( file_exists( $lock ) ) {
    		$astamp = stat($lock);
    		$laststamp = $astamp[9];
    		if ( $laststamp < time() - 60 ) {
    			rmdir( $lock );		# 古いディレクトリの削除
    		}
    	}
     
    	$cnt = 0;
     
    	while( 1 ) {
    		if ( mkdir( $lock ) ) {
    			break;	# 成功
    		}
    		$cnt++;
    		if ( $cnt > $lockcnt ) {
    			$ret = false;	# 失敗
    			break;
    		}
    		sleep(1);
    	}
     
    	return $ret;
     
    }
    ?>