職案人

求職・歴史・仏教などについて掲載するつもりだが、自分の思いつきが多いブログだよ。適当に付き合って下さい。

シンプルな掲示板/Perl.CGIの基礎講座

2018年05月08日 | perl
シンプルな掲示板/Perl.CGIの基礎講座

【環境条件】
Eclipse 4.4(ルナ)
XAMPP 1.8.3 
Perlは既にインストール済み
CGIを使用する時は、必ずApachを起動する

参照サイト→例題9: シンプルな掲示板

【シンプルな掲示板】
1)入力画面

画面上部に投稿用のフォームが表示され、画面下部にこれまでに投稿のあったログが表示されます。投稿用フォームには 「編集キー」 の入力用テキストボックスがあります。投稿する際にこの 「編集キー」 を入力しておけば、この記事を投稿した後で削除したり、修正したりすることができます。また、この掲示板の 「使い方」 のページを表示させることもできます。


■スクリプト「bbs.cgi」
1.#!/usr/local/bin/perl
2.
3.#---- ライブラリの読み込み ----
4.require 'library.pl';
5.
6.#---- グローバル変数の設定 ----
7.$thisfile = 'bbs.cgi';
8.$datafile = 'bbs.dat';
9.$title = 'この講座へのご感想';
10.$maxdata = 20;
11.$maxview = 5;
12.$pass = '0123';
13.$home = 'http://homepage2.nifty.com/Shouji/';
14.$kiji_bg = '#f0e68c';
15.
16.$default_css = < 17.body {background:#eeeeee;}
18.a:link, a:visited {color:blue;}
19.a:hover, a:active {color:red;}
20.END
21.
22.#---- 基本プログラム ----
23.&decode;
24.if ($in{'mode'} eq 'help') {&help;}
25.&dataRead;
26.if ($in{'mode'} eq 'write') {&write;}
27.elsif ($in{'mode'} eq 'edit' || $in{'mode'} eq 'del') {&edit;}
28.&display;
29.
30.#---- データファイル読み取り ----
31.sub dataRead {
32. if (-e $datafile) {
33. open(IN, $datafile);
34. @data = <IN>;
35. close(IN);
36. }
37.}
38.
39.#---- データファイル書き込み ----
40.sub dataWrite {
41. open(OUT, ">$datafile");
42. eval {flock(OUT, 2)} ; # 排他ロック
43. print OUT @data;
44. close(OUT);
45.}
46.
47.#---- 投稿記事の保存処理 ----
48.sub write {
49. $name=$in{'name'}; $ttl=$in{'ttl'}; $kiji=$in{'kiji'}; $pwd=$in{'pwd'};
50. if ($in{'num'}) {
51. # 記事の修正書き込み
52. for ($i=0; $i<@data; $i++) {
53. ($num, $date) = split(/<>/,$data[$i]);
54. if ($in{'num'}==$num) {
55. $data[$i] = "$num<>$date<>$name<>$ttl<>$kiji<>$pwd\n";
56. $in{'page'} = int($i/$maxview) + 1;
57. last;
58. }
59. }
60. } else {
61. # 記事の新規書き込み
62. @youbi = ('日', '月', '火', '水', '木', '金', '土');
63. ($sec, $min, $hour, $day, $mon, $year, $wkday) = localtime(time);
64. $date = sprintf("%4d/%02d/%02d(%s) %02d:%02d:%02d",
65. $year+1900, ++$mon, $day, $youbi[$wkday], $hour, $min, $sec);
66. ($num) = split(/<>/, $data[0]);
67. $num++;
68. unshift(@data, "$num<>$date<>$name<>$ttl<>$kiji<>$pwd\n");
69. while (@data>$maxdata) { pop(@data);}
70. }
71. &dataWrite;
72.}
73.
74.#---- 投稿記事の修正、削除 ----
75.sub edit {
76. for ($i=0; $i<@data; $i++) {
77. ($num, $date, $name, $ttl, $kiji, $pwd) = split(/<>/, $data[$i]);
78. chomp $pwd;
79. if ($num==$in{'num'}) {
80. if ($in{'pwd'} eq $pass || $in{'pwd'} eq $pwd) {
81. if ($in{'mode'} eq 'edit') {
82. # 記事の修正フォーム表示
83. $v0=$num; $v1=$name; $v2=$ttl; $v3=$kiji; $v4=$pwd;
84. &CodeToMj($v1, $v2, $v3, $v4);
85. } else {
86. # 記事の削除
87. splice(@data,$i,1);
88. &dataWrite;
89. }
90. } else {
91. $msg = ($pwd) ? '編集キーが違います':
92. 'この記事には編集キーが設定されていません';
93. &error($msg);
94. }
95. last;
96. }
97. }
98. if ($i==@data) {&error("記事番号 $in{'num'} が見つかりません");}
99. $in{'page'} = int($i/$maxview) + 1;
100.}
101.
102.#---- 投稿フォーム、過去ログの表示 ----
103.sub display {
104. $css = <<END;
105.form {margin:0;}#マージン
106.hr {width:800px;}#水平の罫線
107..tbl {border-collapse:collapse; width:800px;}
108..t1 {background:$kiji_bg; font-weight:bold;}
109..t2 {padding:6px 2em;}
110..t3 {border:1px solid black; text-align:center;}
111.END
112. $java = <<END;
113.function f1() {
114. if (document.forms[0].name.value=="" ||
document.forms[0].kiji.value=="") {
115. alert('名前と記事に入力が必要です'); return false;
116. } else { return true; }
117.}
118.function f2(p) {
119. document.forms[1].page.value = p;
120. document.forms[1].submit();
121.}
122.function f3() {
123. if (document.forms[2].num.value=="" ||
document.forms[2].pwd.value=="") {
124. alert('記事番号と編集キーに入力が必要です'); return false;
125. } else { return true; }
126.}
127.END
128.&header($title, $default_css.$css, $java);
129. print <<END;
130.<body>
131.<p> <a href="$home">HOMEへ戻る</a> 
132.<a href="$thisfile?mode=help">使い方</a></p>
133.<h1 align="center">$title</h1>
134.<form action="$thisfile" method="post" onsubmit="return f1()">
135.<input type="hidden" name="mode" value="write">
136.<input type="hidden" name="num" value="$v0">
137.<table align="center">
138.<tr><td>名前:</td>
139.<td><input type="text" name="name" value="$v1"></td></tr>
140.<tr><td>題名:</td>
141.<td><input type="text" size="40" name="ttl" value="$v2"></td></tr>
142.<tr><td>記事:</td>
143.<td><textarea cols="60" rows="4" name="kiji">$v3</textarea></td></tr>
144.<tr><td>編集キー(記事の修正、削除用):
145.<input type="password" name="pwd" value="$v4">  
146.<input type="submit" value="投稿する"></td></tr>
147.</table>
148.</form>
149.<form action="$thisfile" method="post">
150.<input type="hidden" name="page" value="">
151.</form>
152.END
153. $p = $in{'page'} || 1;
154. $top = --$p * $maxview;
155. for ($i=$top; $i<$top+$maxview; $i++) {
156. if ($i==@data) {last;}
157. ($num, $date, $name, $ttl, $kiji) = split(/<>/, $data[$i]);
158. print <<hr>
160.<table align="center" class="tbl">
161.<tr class="t1"><th width="6%">$num:</th>
162.<td width="20%">$name</td><td width="40%">$ttl</td>
163.<td align="center"><small>$date</small></td></tr>
164.<tr><td class="t2">$kiji</td></tr>
165.</table>
166.END
167. }
168. print qq(<hr>\n<table align="center" cellpadding=4 class="tbl">\n);
169. print qq(<t><td width="45%">【ページ移動】);
170. for ($i=0, $p=1; $i<@data; $i+=$maxview, $p++) {
171. if ($i==$top) {print qq(  $p);}
172. else {print qq(  <a href="javascript:f2($p)">$p</a>);}
173. }
174. print <<END;
175.</td>\n<td class="t3">
176.<form action="$thisfile" method="post" onsubmit="return f3()">
177.記事番号<input type="text" name="num" size="3">
178.編集キー<input type="password" name="pwd">
179.<select name="mode">
180.<option value="edit" selected>修正
181.<option value="del">削除
182.</select> 
183.<input type="submit" value="実行">
184.</form>\n</td></tr>\n</table>
185.</body>\n</html>
186.END
187. exit;
188.}
189.
190.#---- 使い方の表示 ----
191.sub help {
192.&header($title, $default_css, '');
193. print <<END;
194.<body>
195.<p> <a>戻る</a></p>
196.<h1 align="center">使い方</h1>
197.<table align="center"><tr><td>
198.<ul>
199.<li>投稿するには、「名前」と「記事」の入力が必須です。
200.<li>編集キーを入力しておけば、自分の投稿記事を修正したり、削除することができます。
201.<li>記事は最大で$maxdata件保存され、これを超えた場合は古い記事から削除されます。
202.<li>半角のカタカナは使わないでください。文字化けの原因になります。
203.<li>この掲示板に投稿された記事は、管理者が不都合と見なした場合に断りなく
削除または訂正することがあります。
204.<li>管理者は、編集キーの代わりに管理用パスワードを使用して、全ての記事の
修正や削除を行うことが出来ます。
205.</ul>
206.</td></tr></table>\n</body>\n</html>
207.END
208. exit;
209.}
210.
211.#---- ERRORの表示 ----
212.sub error {
213. $css ="h1 {color:red;}\nhr {width:50%;}\n";
214. &header($title, $default_css.$css, '');
215. print <<END;
216.<body>
217.<p> <a>戻る</a></p>
218.<div align="center">
219.<h1>ERROR !</h1>
220.<hr>\n<p><b>$_[0]</b></p>\n<hr>
221.</div>\n</body>\n</html>
222.END
223. exit;
224.}
225.__END__

ライブラリ「library.pl 」

1.#---- フォームデータ取得 ----
2.sub decode {
3. my $buf;
4. if ($ENV{'REQUEST_METHOD'} eq 'POST') {
5. read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});
6. } else {
7. $buf = $ENV{'QUERY_STRING'};
8. }
9. foreach (split(/&/, $buf)) {
10. my ($key, $val) = split(/=/);
11. $val =~ tr/+/ /;
12. $val =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
13. &MjToCode($val);
14. $in{$key} = $val;
15. }
16.}
17.
18.#---- 特定の文字をコードに変換 ----
19.sub MjToCode {
20. foreach (@_) {
21. s/&/&/g;
22. s/ 23. s/>/>/g;
24. s/"/"/g;
25. s/\r\n//g;
26. s/\n//g;
27. }
28.}
29.
30.#---- 特定のコードを文字に変換 ----
31.sub CodeToMj {
32. foreach (@_) {
33. s//\n/g;
34. s/ 35. s/>/>/g;
36. s/"/\"/g;
37. s/&/\&/g;
38. }
39.}
40.
41.#---- HTMLヘッダー部の出力 ----
42.sub header {
43. print <<END;
44.Content-type: text/html
45.
46.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
47.<html>
48.<head>
49.<meta content="text/html; charset=UTF-8">←自分の環境に合わせる
50.<title>$_[0]</title>
51.END

52. if ($_[1]) {
53. print <<END;
<--スタイルシート -->
54.<style type="text/css"></style>
56.END
57. }

58. if ($_[2]) {
59. print <<END;
<--ジャバスクリプト-->
60.<script type="text/javascript"><!--
61.$_[2]//-->
62.</script>
63.END
64. }
65. print qq(</head>\n);
66.}
67.1;


●●●解説●●●

スクリプト「bbs.cgi」の解説

3.#---- ライブラリの読み込み ----
4.require 'library.pl';#ライブラリの読み込み

→この4行目ではrequire関数で外部ファイルであるライブラリを呼び込んでいる。慣例として、ライブラリの拡張子には、.pl を付けることが多いようですが、.cgi を使っても構いません。

【ライブラリについて】

ライブラリ「library.pl」
1.#---- フォームデータ取得 ----
2.sub decode {
 ・・・
16.}
17.
18.#---- 特定の文字をコードに変換 ----
19.sub MjToCode {
 ・・・
28.}
29.
30.#---- 特定のコードを文字に変換 ----
31.sub CodeToMj {
 ・・・
39.}
40.
41.#---- HTMLヘッダー部の出力 ----
42.sub header {
 ・・・
66.}
67.1;

注1)殆どのスクリプトで1行目に書いていた #!/usr/local/bin/perl という記述しない事
注2)ライブラリの最後に行に 1; と記述する事

【全体の流れ】
このスクリプト bbs.cgi の構造はライブラリ library.pl を読み込んだ後、「グローバル変数の設定」 を行い、次の23~28行目の 「基本プログラム」 の部分から、各サブルーチンを呼び出している。

・グローバル変数の設定
このスクリプトで使う変数を定義しているもので、このスクリプトを使うユーザーが任意に設定変更を行えるように、最初にまとめて定義している。
7.$maxdata →投稿記事の最大保存数、
8.$maxview →1ページに表示する投稿記事数
9.$pass →管理者用パスワード、
10.$home →この掲示板の閲覧後に戻すURL
11.$kiji_bg →投稿記事の題名などを表示する行の背景色
など

・スタイルシートの定義
どのページを表示する場合にも使うスタイルシートを定義している。

16.$default_css = <<END;
17.body {background:#eeeeee;}←ブラウザ全体のスタイル調整
18.a:link, a:visited {color:blue;}→
19.a:hover, a:active {color:red;}
20.END

・リンクの4つの状態に対して文字色を指定するには
a:link { color: #0000ff; } … 未訪問のリンク
a:visited { color: #000080; } … 訪問済みのリンク
a:hover { color: #ff0000; } … ポイント時のリンク
a:active { color: #ff8000; } … 選択中のリンク


・基本プログラム
23.&decode;
24.if ($in{'mode'} eq 'help') {&help;}
25.&dataRead;
26.if ($in{'mode'} eq 'write') {&write;}
27.elsif ($in{'mode'} eq 'edit' || $in{'mode'} eq 'del') {&edit;}
28.&display;

プログラムの全体の流れを記述しています。この CGIスクリプトは、処理内容毎にサブルーチンにしておいて、この基本プログラム部分で全体の流れを示し、スクリプトを見直し易くしている。
単純にこの bbs.cgi が呼び出された場合には、mode というキーのハッシュは存在しないため、decode の後で dataRead が行われ、次に display サブルーチンが呼び出されて HTML の画面が生成されることに成る。

初めに、
23行目の&decodeでdecode サブルーチンを呼び出し、フォームから送られてくるデータをハッシュ %in() に格納します。このスクリプトの動作モードを指定するキーワードとして、mode という言葉を使っているので、$in{'mode'} の値を調べれば、処理の分岐を行えるようになっています。

処理の分岐
1)$in{'mode'}== 'help' の場合→「使い方」 の画面を表示して終了します(24行目)。
2)'help' 以外→&dataReadでデータファイルを読み込む(25行目)。
3)$in{'mode'} =='write' の場合→write サブルーチンの処理をする(26行目)。
4)値が'write'以外で、'edit'または'del'ならば→editサブルーチンを呼び出して、投稿済記事の 「修正」あるは「削除」の処理をする(27行目)。

基本プログラムの終わりは、28.&displayより、displayサブルーチンを呼び出し、画面上部に投稿用のフォーム、その下に過去ログを表示するHTML文書を生成して終わる。

・特殊リテラル
スクリプト「bbs.cgi」の最終行(231行目)にある __END__ 特殊リテラルと呼ばれるもので、これ以降はコメントとして無視されるという働きをします。

ライブラリlibrary.plの解説
1.#---- フォームデータ取得 ----
2.sub decode {
3. my $buf;
4. if ($ENV{'REQUEST_METHOD'} eq 'POST') {
5. read(STDIN, $buf, $ENV{'CONTENT_LENGTH'});
6. } else {
7. $buf = $ENV{'QUERY_STRING'};
8. }
9. foreach (split(/&/, $buf)) {
10. my ($key, $val) = split(/=/);
11. $val =~ tr/+/ /;
12. $val =~ s/%([0-9a-fA-F][0-9a-fA-F])/chr(hex($1))/eg;
13. &MjToCode($val);
14. $in{$key} = $val;
15. }
16.}
17.

decodeサブルーチン内の3行目と10行目にあるmyは、
変数の局所化を意味し、ローカル宣言したブロック内でのみ有効、ブロックの外に出ると変数は消滅する。この為、library.pl を利用するスクリプトの中で、同じ名前の変数が使われても問題は起こらない。
また、decodeサブルーチンは、以前にも説明した様にフォームデータの取得の処理をしているが、これまでのスクリプトと違うのは、13行目で MjToCode サブルーチンを呼び出しと、CodeToMj サブルーチンについて説明します

・MjToCode サブルーチンや CodeToMj サブルーチンの解説
18.#---- 特定の文字をコードに変換 ----
19.sub MjToCode {
20. foreach (@_) {
21. s/&/&/g;
22. s/ 23. s/>/>/g;
24. s/"/"/g;
25. s/\r\n//g;
26. s/\n//g;
27. }
28.}
29.
MjToCode サブルーチンは、
投稿された記事の中にあるかもしれない 「<」、「>」、「"」 というような文字を、特殊な書き方に変換するためのものです。即ち、これらの文字はHTMLタグ用の文字であるため、このような文字そのままではHTML文書に書くことはできません。これらを文字として表示するには、「<」、「>」、「"」 と書く、また 「&」 は 「&」 と書くというのが HTML文書のルールなのです。このための変換が、21~24行目で行われています。
また、
記事に書かれているかも知れない改行文字を、HTML文書での に変換する処理を、25、26行目で行っています。

30.#---- 特定のコードを文字に変換 ----
31.sub CodeToMj {
32. foreach (@_) {
33. s//\n/g;
34. s/ 35. s/>/>/g;
36. s/"/\"/g;
37. s/&/\&/g;
38. }
39.}
CodeToMj サブルーチンは、
MjToCode サブルーチンで変換された特殊な書き方を元に戻す逆変換を行います。

■ HTMLヘッダー部の出力
41.#---- HTMLヘッダー部の出力 ----
42.sub header {
43. print <<END;
44.Content-type: text/html
45.
46.<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
47.<html>
48.<head>
49.<meta http-equiv="content-type" content="text/html; charset=UTF-8">
50.<title>$_[0]</title>
51.END
52. if ($_[1]) {
<!-- CSSを埋め込む-->
53. print <<END;
54.<style type="text/css"><!--
55.$_[1]--></style>
56.END
57. }
58. if ($_[2]) {
<!-- JavaScriptを埋め込む-->
59. print <<END;
60.<script type="text/javascript"><!--
61.$_[2]//-->
62.</script>
63.END
64. }
65. print qq(</head>\n);
66.}
67.1;
このサブルーチンは、サブルーチンdisplay、help、error から、&header($title, $default_css.$css, $java)の様に、3つの引数を付けて呼び出されます。第1引数はタイトル、第2引数はスタイルシートの内容、第3引数は javascript の関数定義。

引数は @_ という特殊な配列に積み上げられてサブルーチンに渡されます。第1引数は $_[0] で、第2引数は $_[1] で、第3引数は $_[2] で参照することができます。

43~51行目で、CGIヘッダー と HTML の <title>~</title> までを出力します。
52~57行目で、第2引数 $_[1] が空文字でない場合に、スタイルシートの文字列を出力します。
58~64行目で、第3引数 $_[2] が空文字でない場合に、javascriptの関数定義の文字列を出力します。
最後に65行目で </head> を出力します。このようにして、このサブルーチンを呼び出した側では、<body> タグから書き始めればよいことになります

■複数人によるファイルの同時読み書きするには
複数の人が同じCGIスクリプトファイルを同時に実行する場合は
CGIスクリプトファイルが同じでも、全く別々の環境で実行されますから、何の問題も起きません。しかし、データファイルを同時に操作する場合は、問題が起こる可能性があります。例えば、ファイルを書き込みモードで開くと、ファイルの中身は空にされるため、その直後に他の人が読み込みモードで同じファイルを開けば、空のファイルを読み出すことに成る。

30.#---- データファイル読み取り ----
31.sub dataRead {
32. if (-e $datafile) {
33. open(IN, $datafile);
34. @data = <IN>;
35. close(IN);
36. }
37.}
32行目にある if 文で、ファイルの存在を確認してからファイルを開くようにしています。ファイルが存在しない場合には、@data は空の配列となります。

39.#---- データファイル書き込み ----
40.sub dataWrite {
41. open(OUT, ">$datafile");
42. eval {flock(OUT, 2)} ; #排他ロック
43. print OUT @data;
44. close(OUT);
45.}
dataWrite サブルーチンでは、flock 関数を使ってファイルを排他ロックして、書き込みモードで開いている間は、他人が同じファイルを開けないようにしている。

eval { } は、ブロックの中のプログラムを実行、評価する関数で、ブロック内でエラーが発生してもプログラムは終了しません。そのため、使えない可能性のある関数を実行するときには、eval { } を使うようにします。但し、flock 関数が使えない場合には、42行目の記述は無意味になるため、上述のような不幸なタイミングに遭遇したら諦めてもらうことになります。また、flock 関数の第2引数に指定する番号2は、排他ロックを意味する。

■ 記事の投稿フォーム
102.#---- 投稿フォーム、過去ログの表示 ----
103.sub display {
変数$css にスタイルシートの文字列を格納している
104. $css = <<END;←ヒアドキュメント
 ・・・・・・・
111.END
javascriptの関数定義を変数 $java に格納する
112. $java = <<END;←ヒアドキュメント
113.function f1() {
114. if (document.forms[0].name.value=="" ||
document.forms[0].kiji.value=="") {
115. alert('名前と記事に入力が必要です'); return false;
116. } else { return true; }
117.}
118.function f2(p){
 ・・・・・・
}
122.function f3() {
・・・・・・・
}
127.END
128.&header($title, $default_css.$css, $java);
129. print <<END;
130.<body>
131.<p> <a href="$home">HOMEへ戻る</a> 
132.<a href="$thisfile?mode=help">使い方</a></p>
133.<h1 align="center">$title</h1>

134.<form action="$thisfile" method="post" onsubmit="return f1()">
135.<input type="hidden" name="mode" value="write">
136.<input type="hidden" name="num" value="$v0">
137.<table align="center">
138.<tr><td>名前:</td>
139.<td><input type="text" name="name" value="$v1"></td></tr>
140.<tr><td>題名:</td>
141.<td><input type="text" size="40" name="ttl" value="$v2"></td></tr>
142.<tr><td>記事:</td>
143.<td><textarea cols="60" rows="4" name="kiji">$v3</textarea>
</td></tr>
144.<tr><td>編集キー(記事の修正、削除用):
145.<input type="password" name="pwd" value="$v4">  
146.<input type="submit" value="投稿する"></td></tr>
147.</table>
148.</form>

149.<form action="$thisfile" method="post">
150.<input type="hidden" name="page" value="">
151.</form>
152.END
・・・・
176.<form action="$thisfile" method="post" onsubmit="return f3()">
177.記事番号<input type="text" name="num" size=3>
178.編集キー<input type="password" name="pwd">
179.<select name="mode">
180.<option value="edit" selected>修正
181.<option value="del">削除
182.</select> 
183.<input type="submit" value="実行">
184.</form>\n</td></tr>\n</table>
185.</body>\n</html>
186.END
187. exit;
【解説】
→112行の$java = <<END;~127行のEND7までのjavascriptの関数を、変数 $javaに格納している。これらは、128行目の&header() 呼び出しの引数に使われ、HTML のヘッダー部に記述されます。第2引数の指定に使われている 「. 」 は、文字列結合演算子です。

→このスクリプトが出力する HTML文書には、 <form・・・>~</form> で表される3つのフォームがあります。最初に現われるのが134~148行目で、この最初のフォームが 「投稿フォーム」 を形作っています。 javascriptでは、フォームは forms という配列で表現することができ、現われる順に forms[0]、forms[1] として識別できます。

→146行目の 「投稿する」 と表示された submit ボタンを押すと、データの送信を開始しようとするイベントが発生し、134行目に書かれている onsubmit イベントハンドラ が、113~117行目の f1() 関数を起動します。

→この関数は、「名前」と「記事」の入力状況を調べ、入力がなければ警告を出して false を返し、入力がある場合には true を返します。従って、onsubmit="return f1()" としているため、onsubmit には f1() の戻り値が返され、true の場合のみデータの送信が実行され、false の場合には submit ボタンの操作が無効になります。

→f1() 関数による入力の有無を確認する処理などは、javascript を使わずに、CGIスクリプトで行っても良さそうですが、このように javascript で制御すれば僅かでもサーバーの負荷を減らすことになります。 JavaScript

→132行目のリンクの貼り方(<a href="$thisfile?mode=help">)はGET でデータを渡すことに成る。

→変数$v0, $v1, $v2, $v3, $v4 は、投稿済記事の修正の場合に値がセットされる変数で、新規投稿の場合にはこれらには空文字がセットされます

■ 過去ログの表示
102.#---- 投稿フォーム、過去ログの表示 ----
113.function f1() {
 ・・・・・・
117.}
118.function f2(p){
118. document.forms[1].page.value = p;
120. document.forms[1].submit();
 
}
122.function f3() {
if (document.forms[2].num.value=="" ||
document.forms[2].pwd.value=="") {
124. alert('記事番号と編集キーに入力が必要です'); return false;
125. } else { return true; }
}

153~167行目が過去ログ表示の部分
153. $p = $in{'page'} || 1;
154. $top = --$p * $maxview;
155. for ($i=$top; $i<$top+$maxview; $i++) {
156. if ($i==@data) {last;}
157. ($num, $date, $name, $ttl, $kiji) = split(/<>/, $data[$i]);
158. print <<hr>
160.<table align="center" class="tbl">
161.<tr class="t1"><th width="6%">$num:</th>
162.<td width="20%">$name</td><td width="40%">$ttl</td>
163.<td align="center"><small>$date</small></td></tr>
164.<tr><td class="t2">$kiji</td></tr>
165.</table>
166.END
167. }
・・・・・
ページ移動
168. print qq(<hr>\n<table align="center" cellpadding=4 class="tbl">\n);
169. print qq(<t><td width="45%">【ページ移動】);
170. for ($i=0, $p=1; $i<@data; $i+=$maxview, $p++) {
171. if ($i==$top) {print qq(  $p);}
172. else {print qq(  <a href="javascript:f2($p)">$p</a>);}← JavaScript擬似URL
173. }

174. print <<END;
175.</td>\n<td class="t3">
176.<form action="$thisfile" method="post" onsubmit="return f3()">
177.記事番号<input type="text" name="num" size="3">
178.編集キー<input type="password" name="pwd">
179.<select name="mode">
180.<option value="edit" selected>修正
181.<option value="del">削除
182.</select> 
183.<input type="submit" value="実行">
184.</form>\n</td></tr>\n</table>
185.</body>\n</html>
186.END


【解説】
→$maxviewには1ページに表示する件数が設定してある(11.$maxview = 5;)。なぜなら、保存してあるログの件数が多くなると、それらを1ページに全て表示したのでは見難くなるため

→153行目の$pは、最初のページを1とするページ番号。
154行目の$topは、そのページの最初に表示するログが @data の何番目かを表す変数。
よって、次のfor文は、@dataの$top 番目から$maxview 件数を読み出して表示している。

→172行目のelse {print qq( <a href="javascript:f2($p)">$p</a>);}のリンク部分はjavascriptのf2(p)関数を起動させる。この関数は、forms[1] 即ち 149~151行目のフォームの中にあるpage 名付けられた input タグの属性 value を、引数の値 p に設定した後、submit メソッドでデータ送信を実行します。この様なリンクの貼り方をJavaScript擬似URLと言う

→176~184行目のフォームは、「記事番号」と「編集キー」を入力させてから、投稿済記事の 「修正」 または「削除」の 「実行」を行うためのものです。
「投稿フォーム」の「投稿する」 ボタンの場合と同様に、javascript の関数を使って、「記事番号」 と「編集キー」 が入力済かどうかを確認した後で、データの送出が行われます。

→183行目のsubmit ボタンを押すと、176行目に書かれているonsubmitイベントハンドラが、122~126行目の f3() 関数を起動します。この関数は、「記事番号」 と 「編集キー」 の入力状況を調べ、true または false を返します。その戻り値が、onsubmit イベントハンドラに返され、true の場合のみデータの送信が実行され、false の場合には submit ボタンの操作が無効になります

■ 投稿記事の保存処理
投稿用フォームから送られてくるデータは、名前、題名、記事、編集キーの4つの入力データと、mode および num の隠しデータです。 新規投稿の場合はnum の値は空文字、修正投稿の場合は num の値は修正しようとする記事の番号を表すようにしています。50行目のif文で、新規投稿か、修正投稿かを判断して処理を分岐しています。

尚、投稿記事をデータファイルに保存するときの形式は、「番号<>日付<>名前<>題名<>記事<>編集キー\n」 とし、新しい投稿記事はファイルの先頭に追加するようにします。区切りの文字列として 「<>」 を使っているのは、若しこれと同じ文字列が投稿記事の中に現れても、それは 「<>」 と変換され、この文字列 「<>」 がデータの一部として存在することは有り得ないからです。

47.#---- 投稿記事の保存処理 ----
48.sub write {
49. $name=$in{'name'}; $ttl=$in{'ttl'}; $kiji=$in{'kiji'}; $pwd=$in{'pwd'};
50. if ($in{'num'}) {
51. # 記事の修正書き込み
52. for ($i=0; $i<@data; $i++) {
53. ($num, $date) = split(/<>/,$data[$i]);
54. if ($in{'num'}==$num) {
55. $data[$i] = "$num<>$date<>$name<>$ttl<>$kiji<>$pwd\n";
56. $in{'page'} = int($i/$maxview) + 1;
57. last;
58. }
59. }
60. } else {
61. # 記事の新規書き込み
62. @youbi = ('日', '月', '火', '水', '木', '金', '土');
63. ($sec, $min, $hour, $day, $mon, $year, $wkday) = localtime(time);
64. $date = sprintf("%4d/%02d/%02d(%s) %02d:%02d:%02d",
65. $year+1900, ++$mon, $day, $youbi[$wkday], $hour, $min, $sec);
66. ($num) = split(/<>/, $data[0]);
67. $num++;
68. unshift(@data, "$num<>$date<>$name<>$ttl<>$kiji<>$pwd\n");
69. while (@data>$maxdata) { pop(@data);}
70. }
71. &dataWrite;
72.}
→52~59行目が修正投稿の処理で、ここでは、保存データの配列 @data を調べて一致する記事番号を探し出し、名前、題名、記事、編集キー を書き換えれば良いわけです。

実際にはデータ数だけ回す for ループを作り、$data[$i] が該当することが分かったなら、この $data[$i] を書き替えて for ループを抜け、71行目でデータファイルを書き替えています。

56行目は、「過去ログの表示」 でこのデータが表示されるようにするページ番号処理です。

62~69行目が新規投稿の処理で、ここでは、記事番号の採番と日付の取得が必要です。

日付と時刻は、例題2の 「時間処理の関数」 で説明したように、time 関数で取得した1970年1月1日の0時0分0秒から現在の時刻までの秒数を、localtime 関数に引数として渡して取得します。


63行目で localtime(time) の演算結果を、($sec, $min, $hour, $day, $mon, $year, $wkday) で受け取っているため、秒、分、時、日、月、年、曜日 の値が、夫々の変数に代入されます。64行目では、sprintf 関数を使って、フォーマットした文字列を $date に代入しています。sprintf 関数の第1引数 "%4d/%02d/%02d(%s) %02d:%02d:%02d" でフォーマット形式を指定して、「2007/11/04(日) 10:05:02」 というような文字列を作り出しています。

新しい投稿記事はファイルの先頭に追加するようにします。従って、$data[0] が最新の過去ログですから、66行目でこのデータの記事番号を調べ、67行目でそれに 1 を加えて、新規投稿記事の番号としています。

68行目の unshift(@data,"$num<>$date<>$name<>$ttl<>$kiji<>$pwd\n"); は、unshift 関数で、配列 @data の先頭に新規の投稿記事データを追加しています。69行目は、データの数が最大保存数を超えた場合に、古いデータを削除するための記述で、ここでは pop 関数を使っています

■ 投稿記事の修正、削除
74.#---- 投稿記事の修正、削除 ----
75.sub edit {
76. for ($i=0; $i<@data; $i++) {
77. ($num, $date, $name, $ttl, $kiji, $pwd) = split(/<>/, $data[$i]);
78. chomp $pwd;
79. if ($num==$in{'num'}) {
80. if ($in{'pwd'} eq $pass || $in{'pwd'} eq $pwd) {
81. if ($in{'mode'} eq 'edit') {
82. # 記事の修正フォーム表示
83. $v0=$num; $v1=$name; $v2=$ttl; $v3=$kiji; $v4=$pwd;
84. &CodeToMj($v1, $v2, $v3, $v4);
85. } else {
86. # 記事の削除
87. splice(@data,$i,1);
88. &dataWrite;
89. }
90. } else {
91. $msg = ($pwd) ? '編集キーが違います':
92. 'この記事には編集キーが設定されていません';
93. &error($msg);
94. }
95. last;
96. }
97. }
98. if ($i==@data) {&error("記事番号 $in{'num'} が見つかりません");}
99. $in{'page'} = int($i/$maxview) + 1;
100.}

 フォームからのデータは、修正の場合は mode=edit、削除の場合は mode=del として、記事番号 num と編集キー pwd が送られてきます。記事番号と編集キーの確認の後、修正の場合は投稿用フォームに該当記事の内容を表示して修正の入力を求めるようにします。削除の場合は、保存用データファイルから該当記事を削除します。

for ループ内の最初の77行目で、保存データの配列 @data の各要素を、split 関数を使って $numや $date などに分解し、78行目で $pwd の後ろに付いている改行記号を取り除きます。79行目で、この $num がフォームから送られてきた $in{'num'} に一致するかが調べられ、一致するものが見付かるまで for ループが繰り返されます。

一致するものが見つからなかった場合は、$i が配列 @data の要素数となってループを抜けますから、98行目でエラー表示のサブルーチン error に飛ばします。

該当する記事番号が見つかれば、80行目で編集キーの確認を行います。フォームから送られてきた $in{'pwd'} が、管理者用のパスワードか、データファイルに保存されていた編集キーと一致するか否かで分岐しています。

一致しない場合には、93行目で error サブルーチンに飛ばします。一致した場合には、修正または削除の処理を行います。87, 88行目が削除の処理です。

87行目の splice 関数は、@data の $i 番目の要素から1つの要素を削除します。88行目で、この配列 @data をデータファイルに書き込み、95行目の last; で for ループを抜け、98行目に移ります。

83, 84行目が修正のための処理です。 83行目の $v0 ~ $v4 は、「投稿フォーム」 に該当記事の内容を表示させるための変数です。データファイルには、& < > " というような文字は & < > " に、また改行文字は に変換されて保存されています。このため、84行目の処理で、$v1 ~ $v4 に対して、この逆変換の処理を library.pl の中にある CodeToMj サブルーチンに行わせています。
コメント
  • X
  • Facebookでシェアする
  • はてなブックマークに追加する
  • LINEでシェアする