#! /usr/local/bin/perl
#  ↑この値は設置するサーバに合わせて設定してください。

#########################################################################################################
#
# 簡易予約システム YoYacker Vers.1.11 {FREESOFT} "yoyaku.cgi"
# Copyright(c)2020 by CGI-RESCUE http://www.rescue.ne.jp/
#
# フリーソフトですが利用規定を守ってご利用ください → http://www.rescue.ne.jp/cgi/kitei.shtml
#
# {履歴}
# 2002.06.27 v1.00 リリース
# 2002.07.29 v1.01 時間が重なっているのにエラー処理が出来ていなかったバグの修正
# 2019.12.21 v1.10 セキュリティ上の問題を修正
# 2020.10.01 v1.11 バグの修正 他
#
#########################################################################################################

#------------------------------------------
# 配置例 < >内はパーミッション値の参考値
#
# yoyaku/
#    |--DATA/ <777>
#    |- yoyaku.cgi <755>
#    |- jcode.pl <644>
#------------------------------------------

#配置したアドレス
$address = 'http://設置した場所/yoyaku/yoyaku.cgi';

#データ記録用フォルダ (このフォルダのパーミッションを777相当値に設定してください)
$DATA = './DATA/';

#タイトル
$title = '簡易予約システム';

#会議室のIDを設定 (任意の半角文字列で自由に指定してください)
@HEYA = ("1F-A","1F-B","2F-A","2F-B","2F-C","2F-D");

#会議室のIDに対応する名称を設定
@HEYA_NAME = ("応接室","会議室","映写室1","映写室2","映写室3","映写室4");

#日本語コード変換ライブラリを読み込む
require './jcode.pl';

#---------------------------------------------------------------------------------------------------------

$ext = '.cgi';

#入力
read(STDIN,$buffer,$ENV{'CONTENT_LENGTH'});
#if ($ENV{'QUERY_STRING'} ne '') { $buffer .= "\&$ENV{'QUERY_STRING'}"; }

#入力の解析
@pairs = split(/&/,$buffer);
foreach $pair (@pairs) {

	($key,$val) = split(/=/,$pair);
	$val =~ tr/+/ /;
	$val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;

	#シフトJIS変換
	&jcode'convert(*val,'sjis');
	$val =~ s/<!--(.|\n)*-->//g;
	$val =~ s/\.\.//g;
	$val =~ s/\///g;
	$val =~ s/\n//g;
	$val =~ s/\t//g;

	#連想配列へ格納
	$in{$key} .= "\0" if (defined($in{$key}));
	$in{$key} .= $val;
}

#時刻取得
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);

#初期設定
@wday_array = ('日','月','火','水','木','金','土');
$days[4] = $days[6] = $days[9] = $days[11] = 30;
$days[1] = $days[3] = $days[5] = $days[7] = $days[8] = $days[10] = $days[12] = 31;

#削除処理
if ($in{'action'} eq "delete") {

	if ($in{'DEL'} eq "") { &error2("何もチェックされていません。"); }

	foreach $target (split("\0",$in{'DEL'})) {

		#予約ファイルを読み出す

		$target =~ s/,/\//g;
		if (! open(FILE,"$DATA$target$ext") ) { &error("$!"); }
		while (<FILE>) {

			s/\n//g;
			($key,$val) = split(/\t/,$_,2);
			if ($key eq "pwd") { $PWD = $val; last; }
		}
		close(FILE);

		if ($in{'pwd'} eq $PWD) { push(@UNLINK,"$DATA$target$ext"); }
		else { push(@NG,1); }
	}

	$ok = unlink @UNLINK;
	$ng = @NG;

	if ($ng) { $error = "$ng件の削除に失敗しました。"; }

	&error("$ok件の予\約削除に成功しました。$error");
}

#予約記録処理
elsif ($in{'action'} eq "yoyaku") {

	&c($in{'yyear'},1970,9999);
	&c($in{'ymonth'},1,12);
	&c($in{'yday'},1,31);

	if ($in{'yheya'} =~ /^(\.\.)/) { &error2("不正な文字列があります($in{'yheya'})"); }

	#うるう年の判定
	$days[2] = 28;
	unless ($in{'yyear'} % 4) { $days[2] = 29; }
	unless ($in{'yyear'} % 100) { $days[2] = 28; }
	unless ($in{'yyear'} % 400) { $days[2] = 29; }

	#日数判定
	if ($in{'yday'} > $days[$in{'ymonth'}]) {
		&error2("$in{'yyear'}年の$in{'ymonth'}月は$days[$in{'ymonth'}]日までです。"); }

	if ($in{'uname'} eq "") { &error2("名前を書いてください。"); }
	if ($in{'pwd'} =~ /^$|\W/) { &error2("パスワードは半角英数字で。"); }

	#時分から４桁の数字を形成
	$FROM = $in{'from_hour'} . $in{'from_min'};
	$TO = $in{'to_hour'} . $in{'to_min'};

	if ($FROM >= $TO) { &error2("開始時刻と終了時刻が逆か同じです。"); }

#---------------------------------------------------------------------------------------------------------

	#予約データ(予約開始時刻)の取得
	opendir(DIR,"$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/");
	@BASE3 = readdir(DIR);
	close(DIR);

	foreach $file (@BASE3) {

		$END = "";
		if ($file !~ /^(\d\d\d\d)/) { next; }
		($FILE,$v) = split(/\./,$file,2);

		#予約ファイルを読み出す
		open(FILE,"$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/$file");
		while (<FILE>) {

			s/\n//g;
			($key,$val) = split(/\t/,$_,2);
			if ($key eq "end") { $END = $val; last; }
		}
		close(FILE);

		#予約単位である10分毎に使用中時刻をマークする
		foreach $a ($FILE .. $END) {

			if ($a % 10) { next; }
			if (substr($a,-2,2) >= 60 && substr($a,-2,2) <= 90) { next; } # ６０進数なので…
			$a = sprintf("%04d",$a); # ４桁形成
			$RESV{$a} = 1; # 時間が埋まっているというマークつける
		}
	}

	#予約しようとする時間域のマークをチェックする
	for ($a = $FROM; $a <= $TO; $a = $a + 10) {

		$a = sprintf("%04d",$a);
		if ($RESV{$a}) { &error2("予\約時間に重なっています。"); }
	}

#---------------------------------------------------------------------------------------------------------

	if (!-e "$DATA") { &error("データフォルダがありません。"); }

	#マスク値
	umask(000);

	#予約する部屋のデータ域を作成
	if (!-e "$DATA$in{'yheya'}/") {

		if (! mkdir("$DATA$in{'yheya'}/",777)) {
			&error("$in{'yheya'}が作成できませんでした。"); }
		chmod(0777,"$DATA$in{'yheya'}/");
	}

	#部屋のデータ域の中に予約する日のデータ域を作成
	if (!-e "$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/") {

		if (! mkdir("$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/",777)) {
			&error("$in{'yyear'}$in{'ymonth'}$in{'yday'}が作成できませんでした。"); }
		chmod(0777,"$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/");
	}

	#予約開始時間を名前とした予約データの作成
	if (! open(OUT,"> $DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/$FROM$ext")) {
		&error("$FROMが作成できません。"); }

	print OUT "end\t$TO\n";
	print OUT "admin\t$in{'uname'}\n";
	print OUT "pwd\t$in{'pwd'}\n";
	print OUT "rem\t$in{'rem'}\n";

	close(OUT);
	chmod(0666,"$DATA$in{'yheya'}/$in{'yyear'}$in{'ymonth'}$in{'yday'}/$FROM$ext");

#	&error("$in{'yyear'}年$in{'ymonth'}月$in{'yday'}日に$in{'yheya'}を
#		$FROM〜$TOまで予\約しました。[<a href=\"$address#$jump\">確認する</a>]");

	$jump = $in{'yday'} + 0;
	print "Location: $address#$jump\n\n";

	exit;
}
elsif ($in{'year'} ne "" && $in{'month'} ne "") { # 指定のカレンダー年月指定

	$lyear = $in{'year'};
	$lmonth = $in{'month'};
}
else { # デフォルトのカレンダー年月指定(今月)

	($lsec,$lmin,$lhour,$lmday,$lmon,$lyear,$lwday,$lyday,$lisdst) = localtime(time);

	$lyear += 1900;
	$lmonth = $lmon + 1;
}

#カレンダー表示へ
&LIST($lyear,$lmonth);

sub LIST {

local ($nyear,$nmonth) = @_;

if ($nyear == 0 || $nmonth == 0) { &error("ERROR"); }
($wday) = &getwday($nyear,$nmonth,1); # 曜日判定

#桁数形成
if ($nmonth < 10) { $nmonth = "0$nmonth"; }

#予約データ(部屋)の取得
if (!opendir(DIR,"$DATA")) { &error("ERROR $!"); }
@BASE1 = readdir(DIR);
close(DIR);

foreach $dir (@BASE1) {

	if ($dir =~/\./) { next; }

	#予約データ(日付)の取得
	opendir(DIR,"$DATA$dir/");
	@BASE2 = readdir(DIR);
	close(DIR);

	foreach $dir2 (@BASE2) {

		if ($dir2 =~ /^($nyear$nmonth\d\d)$/) {

			#予約データ(予約開始時刻)の取得
			opendir(DIR,"$DATA$dir/$dir2/");
			@BASE3 = readdir(DIR);
			close(DIR);

			foreach $file (@BASE3) {

				if ($file !~/^\d\d\d\d$ext$/) { next; }
				($file,$d) = split(/$ext/,$file,2);

				#その日のその会議室の全予約開始時刻の取得
				$YS{$dir}{$dir2} .= "\0" if (defined($YS{$dir}{$dir2}));
				$YS{$dir}{$dir2} .= $file;
			}
		}
	}
}

print "Content-type: text/html\n\n";
print "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=x-sjis\"></head>\n";
print "<body>\n";

print "<h1>$title</h1>\n"; # タイトル表示

$now_year = $year + 1900;
$next_year = $now_year + 1;

print <<"EOF";
<form method="POST" action="$address">
予\約する日 <select name="yyear" size=1>
<option value="$now_year" selected>$now_year
<option value="$next_year">$next_year
</select>年
<select name="ymonth" size=1>
EOF

foreach (1..12) {

	if ($_ < 10) { $_ = "0$_"; }
	if ($_ == $mon +1) { $moji = " selected"; } else { $moji = ""; }
	print "<OPTION VALUE=$_$moji>$_</OPTION>\n";
}

print <<"EOF";
</select>月
<select name="yday" size=1>
EOF

foreach (1..31) {

	if ($_ < 10) { $_ = "0$_"; }
	if ($_ == $mday) { $moji = " selected"; } else { $moji = ""; }
	print "<OPTION VALUE=$_$moji>$_</OPTION>\n";
}

print <<"EOF";
</select>日<p>

<select name="from_hour" size=1>
<OPTION VALUE="01" selected>01</OPTION>
EOF

foreach (2..23) {

	if ($_ < 10) { $_ = "0$_"; }
	print "<OPTION VALUE=$_>$_</OPTION>\n";
}

print <<"EOF";
</select>時

<select name="from_min" size=1>
<OPTION VALUE="00" selected>00</OPTION>
<OPTION VALUE="10">10</OPTION>
<OPTION VALUE="20">20</OPTION>
<OPTION VALUE="30">30</OPTION>
<OPTION VALUE="40">40</OPTION>
<OPTION VALUE="50">50</OPTION>
</select>分から

<select name="to_hour" size=1>
<OPTION VALUE="01" selected>01</OPTION>
EOF

foreach (2..23) {

	if ($_ < 10) { $_ = "0$_"; }
	print "<OPTION VALUE=$_>$_</OPTION>\n";
}

print <<"EOF";
</select>時

<select name="to_min" size=1>
<OPTION VALUE="00" selected>00</OPTION>
<OPTION VALUE="10">10</OPTION>
<OPTION VALUE="20">20</OPTION>
<OPTION VALUE="30">30</OPTION>
<OPTION VALUE="40">40</OPTION>
<OPTION VALUE="50">50</OPTION>
</select>分まで<br>

会議室名 <select name="yheya" size=1>
EOF

foreach $n (0 .. $#HEYA) {

	print "<option value=\"$HEYA[$n]\">$HEYA_NAME[$n]</option>\n";
}

print <<"EOF";
</select><p>

予\約者   <input type=text         size=30 name="uname"><br>
パスワード <input type=password size=10 name="pwd"><br>
備考        <input type=text         size=60 name="rem"><p>

※ 開始時刻と終了時刻は重なることは出来ません.<p>

<input type=hidden name="action" value="yoyaku">
<input type=submit value="予\約する">

</form>
EOF

$time = time;
print "<div align=right>[<a href=$address\?$time>最新の状態</a>]</div>\n";

print "<hr>\n";

$next_y = $lyear;
$next_m = $lmonth + 1;
if ($next_m > 12) { $next_y++; $next_m = 1; }

print <<"EOF";
<table width=100% cellpadding=5 cellspacing=0>
<tr bgcolor=gray>
<FORM METHOD=POST ACTION="$address">
<INPUT TYPE=HIDDEN NAME="year" VALUE="$next_y">
<INPUT TYPE=HIDDEN NAME="month" VALUE="$next_m">
<TD><font color=white>予\約カンレンダー $nyear年$nmonth月</font></TD>
<TD align=right><INPUT TYPE=SUBMIT VALUE="$next_m月→"></TD>
</FORM>
</tr></table>

<form method=POST action="$address">
<input type=hidden name="action" value="delete">
EOF

print "<TABLE BORDER=3 CELLPADDING=5 CELLSPACING=2 WIDTH=100%>\n";

print "<TR>\n";
print "<TD>&nbsp;</TD>\n";

foreach $n (0 .. $#HEYA) {

	print "<TH ALIGN=CENTER>$HEYA_NAME[$n]</TH>\n";
}

print "</TR>\n";

#カレンダー表示
foreach $nday (1 .. $days[$lmonth]) {

	if ($nyear == $year + 1900 && $nmonth == $mon + 1 && $nday == $mday) { $COLOR = "#ffffdd"; } # 今日の色
	elsif ($wday == 0) { $COLOR = "#ffdddd"; } # 日曜日の色
	elsif ($wday == 6) { $COLOR = "#ddddff"; } # 土曜日の色
	else { $COLOR = "#dddddd"; } # その他

	print "<TR>\n";
	print "<TD ALIGN=CENTER VALIGN=TOP BGCOLOR=$COLOR WIDTH=50><a name=\"$nday\">$nday日($wday_array[$wday])</a></TD>\n";

	foreach $n (0 .. $#HEYA) {

		print "<TD VALIGN=TOP>";

		$jikan = sprintf("%04d%02d%02d",$nyear,$nmonth,$nday);

		foreach $file (sort split("\0",$YS{$HEYA[$n]}{$jikan})) {

			#予約ファイルを読み出す
			open(FILE,"$DATA$HEYA[$n]/$jikan/$file$ext");

			undef %FILE;

			while (<FILE>) {

				s/\n//g;
				($key,$val) = split(/\t/,$_,2);

				$FILE{$key} = $val;
			}
			close(FILE);

			$file2 = $file;
			$end2 = $FILE{'end'};

			1 while $file2 =~ s/(\d\d)(\d\d)/$1:$2/g;
			1 while $end2 =~ s/(\d\d)(\d\d)/$1:$2/g;

			if ($FILE{'rem'} ne "") { $rem = "\n<br>[備考] $FILE{'rem'}"; } else { $rem = ""; }

			print "<nobr><font size=-1><input type=checkbox name=DEL value=\"$HEYA[$n],$jikan,$file\"><b><u>$file2〜$end2</u></b><br>\n[予\約者] $FILE{'admin'}$rem</font></nobr><br>\n";
		}

		print "&nbsp;</TD>\n";
	}

	print "</TR>\n";

	#次の日
	$wday++;
	if ($wday > 6) { $wday = 0; }
}

print "</TABLE><P>\n";

print <<"EOF";
削除する予\約にチェックしパスワードを指定してボタンを押してください。<br>
パスワード <input type=password name=pwd size=10>
<input type=submit value="削除">
EOF

# ↓ この著作表示とリンクを削除すると利用規定違反です。
print <<"EOF";
<div align=right><font size=-1><a href="https://www.rescue.ne.jp/" target=_blank alt="(c)2019 CGI-RESCUE">YoYacker</a></font></div>
</form>
</BODY>
</HTML>
EOF

}

exit;


sub getwday { # 曜日判定

	local ($year,$month,$day) = @_;

	&c($year,1970,9999);
	&c($month,1,12);
	&c($day,1,31);

	local ($base,$i,$wday);

	#うるう年の判定
	$days[2] = 28;
	unless ($year % 4) { $days[2] = 29; }
	unless ($year % 100) { $days[2] = 28; }
	unless ($year % 400) { $days[2] = 29; }

	#基本年からの通算日数(該当前年まで)
	$base = (($year - 1) * 365) + int(($year - 1) / 4) - int(($year - 1) / 100) + int(($year - 1) / 400);

	#そこへ該当年の日数を加算
	$i = $month;
	while ( --$i ) { $base += $days[$i]; }

	#１週間７日で除算した余りが曜日
	$wday = ($base + $day) % 7;

	return $wday;
}

sub c {

	local ($n,$s,$e) = @_;

	$n = int($n);
	if ($n < $s) { &error2("不正な値があります($n)"); }
	elsif ($n > $e) { &error2("不正な値があります($n)"); }
}

sub error {

	local($msg) = @_;

	print "Content-type: text/html\n\n";
	print "<html><head>\n";
	print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=x-sjis\"></head>\n";
	print "<body>\n";
	print "<h3>$msg</h3>\n";
	print "[<a href=$address\?>再表\示</a>] してください。\n";
	print "</body></html>\n";

	exit;
}

sub error2 {

	local($msg) = @_;

	print "Content-type: text/html\n\n";
	print "<html><head>\n";
	print "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=x-sjis\"></head>\n";
	print "<body>\n";
	print "<h3>$msg</h3>\n";
	print "前のページに戻ってください。\n";
	print "</body></html>\n";

	exit;
}
