#!/usr/bin/perl --

#################################################
##                                             ##
## script name : simple-counter.cgi            ##
## site name   : CGI WEB                       ##
## site url    : http://www.cgi-web.net/       ##
##                                             ##
#################################################

$script  = './simple-counter.cgi';
$version = '2.10';

## ファイル構成例 []=ディレクトリ ()=推奨パーミッション値(設置サーバに合わせて最適値に変更する事)
##
##  index.html(<img>タグを記述するhtml)
##       |
## [simple-counter(755)] / simple-counter.cgi(755)
##       |                 jcode.pl(644)
##       |                 gifcat.pl(644)
##       |
##       +----------------[counter-log  (755)] / datalog.cgi(666)
##       |
##       +----------------[counter-image(755)] / 0.gif〜9.gif(644)
##       |                                       total.gif(644) 合計
##       |                                       today.gif(644) 本日
##       |                                       yesterday.gif(644) 昨日
##       |
##       +----------------[counter-lock (777)]
##
## ※合計(total.gif)・本日(today.gif)・昨日(yesterday.gif)の画像は必ずしも用意する必要はありません。
##
## カウンタ表示タグ例
## <img src="./simple-counter/simple-counter.cgi?page=ページID" />
## ?の後に「page=ページID」を指定して下さい。
## 別のページIDを指定する事で複数ページに設置する事が可能です。
## ページIDは半角英数字とアンダースコア(_)のみ使用可能です。
##
## メンテナンスモードへの入室
## http:// 〜 /simple-counter/simple-counter.cgi?mode=admin
## ?の後に「mode=admin」を指定して下さい。
## カウンタのタイプ選択とカウンタ値の修正を行う事が出来ます。
##
## 携帯端末でメンテナンスモードを使用(au未確認)
## http:// 〜 /simple-counter/simple-counter.cgi?mode=admin&m=h
## ?の後に「mode=admin&m=h」を指定して下さい。

########################
#ファイルパス設定      #
########################

# ライブラリ取り込み
require './jcode.pl';

# ライブラリ取り込み
require './gifcat.pl';

# ログファイルのパス
$datalog = './counter-log/datalog.cgi';

# ロックファイルのパス
$lock = './counter-lock/lock';

# カウンタ画像を置くディレクトリのパス
$imagedir = './counter-image/';

########################
#動作設定              #
########################

# メンテナンスモードからの戻り先のパス又はURL
$back_url = '';

# メンテナンスパスワード
# 半角英数字のみ
$master_pass = '1234abcd';

# IPアドレスチェック設定
# 0..同一訪問者カウントアップ有り
# 1..同一訪問者カウントアップ無し
$ip_check = 0;

# カウンタ値の表示桁数
$total_size     = 7;  # 合計
$today_size     = 3;  # 本日
$yesterday_size = 3;  # 昨日

# サーバ時刻調整
$ENV{'TZ'} = "JST-9";

########################
#設定完了              #
########################

########################
#メイン処理            #
########################

&decode;

if ($buf{'mode'} eq 'admin')    { &admin; }           # パスワード認証
if ($buf{'button'} eq 'show')   { &show; }            # メンテナンス画面
if ($buf{'button'} eq 'change') { &change; }          # カウンタ修正
if ($buf{'page'})               { &update_counter; }  # カウンタ表示

exit();

########################
#サニタイズ処理        #
########################

sub sanitize {

$_[0] =~ s/&/&amp;/g;
$_[0] =~ s/"/&quot;/g;
$_[0] =~ s/'/&#39;/g;
$_[0] =~ s/</&lt;/g;
$_[0] =~ s/>/&gt;/g;
return($_[0]);

}

########################
#カウンタ修正          #
########################

sub change {

if ($buf{'pass'} ne $master_pass) { exit(); }

# ロック開始
&lock(1);
open(IN, "<$datalog") || &error("Error:ログファイルオープン<br />$!", 'unlock');
@data = <IN>;
close(IN);

my(@new);
foreach (@data) {
    my($id, $ymd, $ip, $total, $today, $yesterday, $type) = split(/<>/, $_);
    if ($buf{'id'} ne $id) { push(@new, $_); next; }
    # 削除時
    if ($buf{'delete'}) { next; }
    # 修正時
    if ($buf{'total'} =~ /\D/)     { $buf{'total'} = $total; }
    if ($buf{'today'} =~ /\D/)     { $buf{'today'} = $today; }
    if ($buf{'yesterday'} =~ /\D/) { $buf{'yesterday'} = $yesterday; }
    $buf{'type'} =~ s/[\r\n]//g;
    $buf{'type'} = &sanitize($buf{'type'});
    my($line) = "$id<>$ymd<>$ip<>$buf{'total'}<>$buf{'today'}<>$buf{'yesterday'}<>$buf{'type'}<>\n";
    push(@new, $line);
}

open(OUT, ">$datalog") || &error("Error:ログファイルオープン<br />$!", 'unlock');
print OUT @new;
close(OUT);
# ロック解除
if (-e $lock) { rmdir($lock); }

&show;

}

########################
#パスワード認証        #
########################

sub admin {

&header;

if ($buf{'m'}) {
    print "<form action=\"$script\" method=\"post\">\n";
    print "Input the password<br />\n";
    print "<input type=\"password\" name=\"pass\" size=\"10\" />\n";
    print "<input type=\"submit\" value=\" 送信 \" />\n";
    print "<input type=\"hidden\" name=\"button\" value=\"show\" />\n";
    print "<input type=\"hidden\" name=\"m\" value=\"h\" />\n";
    print "</form>\n";
    print "</body>\n";
    print "</html>\n";
} else {
    print "<br />\n";
    print "<center>\n";
    print "<hr size=\"2\" width=\"500\" /><br />\n";
    print "Input the password\n";
    print "<form action=\"$script\" method=\"post\">\n";
    print "<input type=\"password\" name=\"pass\" size=\"10\" />\n";
    print "<input type=\"submit\" value=\" 送信 \" />\n";
    print "<input type=\"hidden\" name=\"button\" value=\"show\" />\n";
    print "</form>\n";
    print "<hr size=\"2\" width=\"500\" />\n";
    print "</center><br /><br />\n";
    print "</body>\n";
    print "</html>\n";
}

exit();

}

########################
#メンテナンス画面      #
########################

sub show {

if ($buf{'pass'} ne $master_pass) { &error('Error:パスワード不正'); }

# ロック開始
&lock(1);
open(IN, "<$datalog") || &error("Error:ログファイルオープン<br />$!", 'unlock');
@data = <IN>;
close(IN);
# ロック解除
if (-e $lock) { rmdir($lock); }

&header;

if ($buf{'m'}) {
    print "メンテナンス<br />\n";
} else {
    print "<br />\n";
    print "<center>\n";
    print "<hr size=\"2\" width=\"500\" /><br />\n";
    print "メンテナンス<br /><br />\n";
    print "<hr size=\"2\" width=\"500\" /><br />\n";
}

# ページIDの昇順に並べ替え
my(@new);
@new = sort @data;

foreach (@new) {
    my($id, $ymd, $ip, $total, $today, $yesterday, $type) = split(/<>/, $_);
    print "<form action=\"$script\" method=\"post\">\n";
    print "<input type=\"hidden\" name=\"button\" value=\"change\" />\n";
    print "<input type=\"hidden\" name=\"id\" value=\"$id\" />\n";
    print "<input type=\"hidden\" name=\"pass\" value=\"$buf{'pass'}\" />\n";
    if ($buf{'m'}) {
        print "[${id}]<br />\n";
        print "合計<input type=\"text\" name=\"total\" value=\"$total\" size=\"15\" /><br />\n";
        print "本日<input type=\"text\" name=\"today\" value=\"$today\" size=\"10\" /><br />\n";
        print "昨日<input type=\"text\" name=\"yesterday\" value=\"$yesterday\" size=\"10\" /><br />\n";
        print "<select name=\"type\" size=\"1\">\n";
        if ($type == 0) { print "<option value=\"0\" selected=\"selected\">合計(数値のみ)</option>\n"; } else { print "<option value=\"0\">合計(数値のみ)</option>\n"; }
        if ($type == 1) { print "<option value=\"1\" selected=\"selected\">合計</option>\n"; } else { print "<option value=\"1\">合計</option>\n"; }
        if ($type == 2) { print "<option value=\"2\" selected=\"selected\">本日・昨日</option>\n"; } else { print "<option value=\"2\">本日・昨日</option>\n"; }
        if ($type == 3) { print "<option value=\"3\" selected=\"selected\">合計・本日・昨日</option>\n"; } else { print "<option value=\"3\">合計・本日・昨日</option>\n"; }
        if ($type == 4) { print "<option value=\"4\" selected=\"selected\">本日・昨日・合計</option>\n"; } else { print "<option value=\"4\">本日・昨日・合計</option>\n"; }
        print "</select><br />\n";
        print "<input type=\"hidden\" name=\"m\" value=\"h\" />\n";
    } else {
        print "[${id}]\n";
        print "合計<input type=\"text\" name=\"total\" value=\"$total\" size=\"15\" />\n";
        print "本日<input type=\"text\" name=\"today\" value=\"$today\" size=\"10\" />\n";
        print "昨日<input type=\"text\" name=\"yesterday\" value=\"$yesterday\" size=\"10\" />\n";
        print "<select name=\"type\" size=\"1\">\n";
        if ($type == 0) { print "<option value=\"0\" selected=\"selected\">合計(数値のみ)</option>\n"; } else { print "<option value=\"0\">合計(数値のみ)</option>\n"; }
        if ($type == 1) { print "<option value=\"1\" selected=\"selected\">合計</option>\n"; } else { print "<option value=\"1\">合計</option>\n"; }
        if ($type == 2) { print "<option value=\"2\" selected=\"selected\">本日・昨日</option>\n"; } else { print "<option value=\"2\">本日・昨日</option>\n"; }
        if ($type == 3) { print "<option value=\"3\" selected=\"selected\">合計・本日・昨日</option>\n"; } else { print "<option value=\"3\">合計・本日・昨日</option>\n"; }
        if ($type == 4) { print "<option value=\"4\" selected=\"selected\">本日・昨日・合計</option>\n"; } else { print "<option value=\"4\">本日・昨日・合計</option>\n"; }
        print "</select>\n";
    }
    print "<input type=\"submit\" value=\" 修正 \" />\n";
    print "<input type=\"submit\" name=\"delete\" value=\" 削除 \" />\n";
    print "</form><br />\n";
}

unless ($buf{'m'}) { print "</center>\n"; }
print "</body></html>\n";

exit();

}

########################
#カウンタ表示          #
########################

sub update_counter {

if ($buf{'page'} =~ /\W/) { exit(); }

# ロック開始
&lock(0);

if (!open(IN, "<$datalog")) {
    if (-e $lock) { rmdir($lock); }
    exit();
}
@data = <IN>;
close(IN);

# カウンタ値設定
my(@new);
my($flag) = 0;
my($sec, $min, $hour, $day, $mon, $year, $week) = localtime(time);
my($now_ymd) = sprintf("%04d/%02d/%02d", $year+1900, $mon+1, $day);
my($show_total, $show_today, $show_yesterday, $show_type);

# IP取得
my($get_ip) = $ENV{'REMOTE_ADDR'};
$get_ip =~ s/[\r\n]//g;
$get_ip = &sanitize($get_ip);

foreach (@data) {
    my($id, $ymd, $ip, $total, $today, $yesterday, $type) = split(/<>/, $_);
    if ($id ne $buf{'page'}) { push(@new, $_); next; }
    # ページIDとマッチ
    $flag = 1;
    if ($ip_check && $ip eq $get_ip) {
        # カウンタ値更新なし
        push(@new, $_);
        $show_total     = $total;
        $show_today     = $today;
        $show_yesterday = $yesterday;
        $show_type      = $type;
    } else {
        # カウンタ値更新あり
        if ($ymd eq $now_ymd) {
            # 同日アクセス
            $total++;
            $today++;
            my($line) = "$id<>$ymd<>$get_ip<>$total<>$today<>$yesterday<>$type<>\n";
            push(@new, $line);
            $show_total     = $total;
            $show_today     = $today;
            $show_yesterday = $yesterday;
            $show_type      = $type;
        } else {
            # 翌日アクセス
            $total++;
            $yesterday = $today;
            $today = 1;
            my($line) = "$id<>$now_ymd<>$get_ip<>$total<>$today<>$yesterday<>$type<>\n";
            push(@new, $line);
            $show_total     = $total;
            $show_today     = $today;
            $show_yesterday = $yesterday;
            $show_type      = $type;
        }
    }
}
if ($flag == 0) {
    # ページIDとアンマッチ(既定値)
    my($line) = "$buf{'page'}<>$now_ymd<>$get_ip<>1<>1<>0<>0<>\n";
    push(@new, $line);
    $show_total     = 1;
    $show_today     = 1;
    $show_yesterday = 0;
    $show_type      = 0;
}

# カウンタ値更新
if (!open(OUT, ">$datalog")) {
    if (-e $lock) { rmdir($lock); }
    exit();
}
print OUT @new;
close(OUT);

# ロック解除
if (-e $lock) { rmdir($lock); }

# 表示桁数調節
my(@total)     = split(//, $show_total);
my(@today)     = split(//, $show_today);
my(@yesterday) = split(//, $show_yesterday);
while (@total < $total_size)         { unshift(@total, 0); }
while (@today < $today_size)         { unshift(@today, 0); }
while (@yesterday < $yesterday_size) { unshift(@yesterday, 0); }

# ファイル名格納
my(@GIF);
if ($show_type == 0) {
    # タイプ0(数値のみ)
    foreach (@total)                   { push(@GIF, "${imagedir}${_}.gif"); }
} elsif ($show_type == 1) {
    # タイプ1(合計)
    if (-e "${imagedir}total.gif")     { push(@GIF, "${imagedir}total.gif"); }
    foreach (@total)                   { push(@GIF, "${imagedir}${_}.gif"); }
} elsif ($show_type == 2) {
    # タイプ2(本日・昨日)
    if (-e "${imagedir}today.gif")     { push(@GIF, "${imagedir}today.gif"); }
    foreach (@today)                   { push(@GIF, "${imagedir}${_}.gif"); }
    if (-e "${imagedir}yesterday.gif") { push(@GIF, "${imagedir}yesterday.gif"); }
    foreach (@yesterday)               { push(@GIF, "${imagedir}${_}.gif"); }
} elsif ($show_type == 3) {
    # タイプ3(合計・本日・昨日)
    if (-e "${imagedir}total.gif")     { push(@GIF, "${imagedir}total.gif"); }
    foreach (@total)                   { push(@GIF, "${imagedir}${_}.gif"); }
    if (-e "${imagedir}today.gif")     { push(@GIF, "${imagedir}today.gif"); }
    foreach (@today)                   { push(@GIF, "${imagedir}${_}.gif"); }
    if (-e "${imagedir}yesterday.gif") { push(@GIF, "${imagedir}yesterday.gif"); }
    foreach (@yesterday)               { push(@GIF, "${imagedir}${_}.gif"); }
} elsif ($show_type == 4) {
    # タイプ4(本日・昨日・合計)
    if (-e "${imagedir}today.gif")     { push(@GIF, "${imagedir}today.gif"); }
    foreach (@today)                   { push(@GIF, "${imagedir}${_}.gif"); }
    if (-e "${imagedir}yesterday.gif") { push(@GIF, "${imagedir}yesterday.gif"); }
    foreach (@yesterday)               { push(@GIF, "${imagedir}${_}.gif"); }
    if (-e "${imagedir}total.gif")     { push(@GIF, "${imagedir}total.gif"); }
    foreach (@total)                   { push(@GIF, "${imagedir}${_}.gif"); }
}

# カウンタ画像表示
print "Content-type: image/gif\n\n";
binmode(STDOUT);
print &gifcat'gifcat(@GIF);
close(STDOUT);

exit();

}

########################
#エラー処理            #
########################

sub error {

if ($_[1] eq 'unlock' && -e $lock) { rmdir($lock); }

&header;

if ($buf{'m'}) {
    print "<form action=\"$script\" method=\"post\">\n";
    print "$_[0]<br />\n";
    print "<input type=\"submit\" value=\" 戻る \" />\n";
    print "<input type=\"hidden\" name=\"mode\" value=\"admin\" />\n";
    print "<input type=\"hidden\" name=\"m\" value=\"h\" />\n";
    print "</form>\n";
} else {
    print "<br />\n";
    print "<center>\n";
    print "<hr size=\"2\" width=\"500\" /><br />\n";
    print "$_[0]<br /><br />\n";
    print "<hr size=\"2\" width=\"500\" />\n";
    print "<form action=\"$script\" method=\"post\">\n";
    print "<input type=\"submit\" value=\" 戻る \" />\n";
    print "<input type=\"hidden\" name=\"mode\" value=\"admin\" />\n";
    print "</form>\n";
    print "</center><br /><br />\n";
}

print "</body></html>\n";

exit();

}

########################
#ロック開始            #
########################

sub lock {

if (-e $lock && (stat(_))[9] + 60 < time) { rmdir($lock); }

for (1..20) {
    if (mkdir($lock, 0755)) { return; }
    select undef, undef, undef, 0.5;
}

if ($_[0]) { &error("Error:ロックファイル"); } else { exit(); }

}

########################
#デコード処理          #
########################

sub decode {

if ($ENV{'REQUEST_METHOD'} eq 'POST') {
    read(STDIN, $buffer, $ENV{'CONTENT_LENGTH'});
} elsif ($ENV{'REQUEST_METHOD'} eq 'GET') {
    $buffer = $ENV{'QUERY_STRING'};
}

@pair = split(/&/, $buffer);
foreach (@pair) {
    ($name, $val) = split(/=/, $_);
    $val =~ tr/+/ /;
    $val =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
    &jcode'convert(*val, 'sjis');
    $buf{$name} = $val;
}

}

########################
#ヘッダ処理            #
########################

sub header {

print "Content-type: text/html\n\n";

print <<"EOD";
<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="ja" lang="ja">
<head>
<meta http-equiv="Content-type" content="text/html; charset=Shift_JIS" />
<title>simple counter</title>
<meta name="author" content="CGI WEB [ http://www.cgi-web.net/ ]" />
<meta http-equiv="Content-style-type" content="text/css" />
<style type="text/css">
<!--
a:link    {text-decoration:underline; color:#3030c0}
a:visited {text-decoration:underline; color:#3030c0}
a:active  {text-decoration:underline; color:#f00000}
a:hover   {text-decoration:underline; color:#f00000}
.home     {font-size:12px}
.cgiweb   {font-size:13px}
hr        {color:#9090f0}
body,td   {font-family:Meiryo; font-size:14px}
-->
</style>
</head>
<body text="#3030c0" bgcolor="#eeeeff">
<!--simple counter ver$version-->
EOD

unless ($buf{'m'}) {
    print "<!--削除禁止ここから-->\n";
    print "<table border=\"0\" cellspacing=\"0\" cellpadding=\"0\" width=\"100%\">\n";
    print "<tr><td align=\"left\"><a href=\"$back_url\" target=\"_top\"><font class=\"home\">Home</font></a></td>\n";
    print "<td align=\"right\"><a href=\"http://www.cgi-web.net/\" target=\"_blank\"><strong class=\"cgiweb\">CGI WEB</strong></a></td></tr>\n";
    print "</table>\n";
    print "<!--削除禁止ここまで-->\n";
}

}
