#!/usr/bin/perl -w 
#------------------------------------------------------------------------------
# $Id$
#------------------------------------------------------------------------------
# Download operation-log file CGI
#
# Copyright (c) BAYBITS LLC. All rights reserved.
#------------------------------------------------------------------------------
use strict;
use utf8;
use URI::Escape;
use CGI;
use Time::Local;
use HTML::Template;
use XML::DOM;
use TF::GroupList;
use TF::XMLUtils;
use TF::CommonUtils;
use TF::OplogIndex;
use Encode;
binmode(STDOUT,":utf8");

# CSV ファイルは UTF-8 BOM 付きで出力します.
# 古い Excel 向けの Shift_JIS 出力では、cp932 で表現できない文字が失われます.

# global
my $LOCATION		= $ENV{'TF_LOCATION'} || '';
my $BRAND 			= $ENV{'TF_BRANDNAME'} || '';
my $USERID          = $ENV{'REMOTE_USER'} || '';
my $PASSWD          = $ENV{'TF_USERPLAINPASSWORD'} || '';
my $LANGPARAM		= TF::CommonUtils::getLangPriority();
my ($SCHEMA,$SERVER_PORT,$URI_PREFIX) = TF::CommonUtils::getCgiConnectInfo();
my $URL             = $URI_PREFIX . $LOCATION;
my $USERTYPE		= $ENV{'TF_USERTYPE'} || 'normal';
my $USERPRIVILEGE   = $ENV{'TF_USERPRIVILEGE'} || '';
my $TEMPLATE_FILE 	= './tmpl/opshowlog.tmpl.' . $LANGPARAM;
my $TF_LOGOPERATION	= $ENV{'TF_LOGOPERATION'} || 'off';
my $dir_pass		= $ENV{'TF_LOGFILE_ROOT'} || '/var/log/teamfile';
my $pagetitle		= $ENV{'TF_FOLDER_LOGOPERATION'} || '';
my $PAGETITLE		= TF::CommonUtils::decodeUTF8($pagetitle);
my $PAGEIMG			= $ENV{'TF_FOLDER_LOGOPERATION_IMG'} || '';
my $MSGTABLE_ja 	= {
					CGIError        => "CGIエラーです",
					mErrNoLogEntryT => 'インフォメーション',
					mErrNoLogEntry	=> '対象の操作ログは存在しませんでした。',
					mErrCGI			=> 'CGIモジュールの生成に失敗しました。',
					mErrRangeParam  => '「抽出範囲」が正しく指定されておりません。',
					mErrNoCmd       => '操作パラメータが指定されておりません。',
					mErrInvalidCmd  => '操作パラメータが正しくありませんでした。',
					mErrTargetGroup => '操作ログ対象が指定されておりません。',
					mErrInvalidType => '出力形式が正しくありませんでした。',
					mErrAccessDeny  => '操作ログの表示は制限されています。',
					mErrIndexNotReady => '操作ログインデックスが作成されていません。しばらくしてから再実行してください。',
					mMethodName1	=> '取得',
					mMethodName2	=> '登録',
					mMethodName3	=> 'フォルダ作成',
					mMethodName4	=> '削除',
					mMethodName5	=> '移動',
					mMethodName6	=> '名前変更',
					mMethodName7	=> 'コピー',
					mMethodName8	=> 'ロック',
					mMethodName9	=> 'アンロック',
					mRight			=> '成功',
					mWrong			=> '失敗'
				};
my $MSGTABLE_en 	= {
					CGIError        => "CGI error found.",
					mErrNoLogEntryT => 'Information',
					mErrNoLogEntry  => 'The Operation log entry for this target was not found.',
					mErrCGI			=> 'Failed to create cgi module.',
					mErrRangeParam  => 'Invalid "Range" parameter.',
					mErrNoCmd       => 'The operation parameter was not specified.',
					mErrInvalidCmd  => 'Invalid operation parameter.',
					mErrTargetGroup => 'The target of operation log was not specified.',
					mErrInvalidType => 'Invalid "type" of output.',
					mErrAccessDeny  => 'You could not access this operation log.',
					mErrIndexNotReady => 'The operation log index is not ready. Please try again later.',
					mMethodName1	=> 'GET',
					mMethodName2	=> 'PUT',
					mMethodName3	=> 'MKCOL',
					mMethodName4	=> 'DELETE',
					mMethodName5	=> 'MOVE',
					mMethodName6	=> 'RENAME',
					mMethodName7	=> 'COPY',
					mMethodName8	=> 'LOCK',
					mMethodName9	=> 'UNLOCK',
					mRight			=> 'Success',
					mWrong			=> 'Failure'
			};
my $MSGTABLE		= ($LANGPARAM eq 'ja') ? $MSGTABLE_ja : $MSGTABLE_en;


if (!$LOCATION || !$USERTYPE || $USERTYPE eq 'limited' || $TF_LOGOPERATION ne 'on') {
	TF::CommonUtils::showAccessDeny();
	die'';
}

main();

#
# main
#
sub main {

	# CGIパラメータの取得とvalidation
	my ($cmd, $type, $egid, $logdays, $ecgid, $rec, $log_from, $log_to) = parseCgiParams();
	if (!$cmd) {
		return;
	}

	if ($cmd eq 'locdown') {
		downloadLocationLog($type, $logdays, $log_from, $log_to);
		return;
	}

	my $gid = TF::CommonUtils::decipherNumericValue($egid);

	my $isAlldownload = 0;
	if ($cmd eq 'alldown') {
		$isAlldownload = 1;
	}

	# 操作対象となるグループの名前一覧を取得する
	my ($showname,@webgroup) = getGroupNames($gid, $isAlldownload,$rec);
	if (!$isAlldownload) {
		my $found_gid = 0;
		# 個々のグループをクリックしてダウンロードする場合、$gid が@webgroup に含まれているはず
		foreach (@webgroup) {
			if ($_->{id} eq $gid) {
				$found_gid = 1;
				last;
			}
		}
		if ($found_gid == 0) {
			# 不正アクセスである
			TF::CommonUtils::showErrorPage($MSGTABLE->{mErrAccessDeny}, $LANGPARAM);
			return;
		}
	}

	my @idx_table = ();
	if (readIndexedGroupLogs($logdays, $log_from, $log_to, \@webgroup, \@idx_table)) {
		my @dates = TF::OplogIndex::date_range($logdays, $log_from, $log_to);
		my $csvFileName = buildCsvFileName($dates[0], $dates[$#dates], "oplog.csv");
		if ($type eq "web") {
			outputWeb($showname, \@webgroup, \@idx_table, $ecgid, $logdays, $egid, $type, $rec, $log_from, $log_to);
		}
		else {
			outputCsv(\@idx_table, $csvFileName);
		}
		return;
	}

	my $found		= undef;
	my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$year +=1900;
	$mon++;
	my ($ysec,$ymin,$yhour,$ymday,$ymon,$yyear,$ywday,$yyday,$yisdst) = localtime(time-24*3600);
	$yyear +=1900;
	$ymon++;

	opendir(DIR,"$dir_pass");
	my @dirfile = readdir(DIR);
	@dirfile = sort @dirfile;
	closedir(DIR);
	my $chkwrite = 0;
	my @table = ();
	my %tmp;
	my $openfile;
	my $chklocation = $LOCATION;
	my $csvFileName = "oplog.csv";

	my $i;
	my @range_dates = TF::OplogIndex::date_range($logdays, $log_from, $log_to);
	if (!@range_dates) {
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
		return;
	}
	if ($logdays eq "TODAY") {
		$openfile = sprintf("%04d%02d%02d",$year,$mon,$mday);
		$chkwrite = parseloop($openfile,\@webgroup,$chklocation,\@table,$chkwrite);
		$csvFileName = buildCsvFileName($openfile, $openfile, $csvFileName);
	}
	elsif ($logdays eq "YESTERDAY") {
		$openfile = sprintf ("%04d%02d%02d",$yyear,$ymon,$ymday);
		$chkwrite = parseloop($openfile,\@webgroup,$chklocation,\@table,$chkwrite);
		$csvFileName = buildCsvFileName($openfile, $openfile, $csvFileName);
	}
	elsif ($logdays eq "LASTWEEK") {
		my $fist_name = undef;
		for($i=6;$i>=0;$i--){
			my ($sec7,$min7,$hour7,$mday7,$mon7,$year7,$wday7,$yday7,$isdst7) = localtime(time-24*3600*$i);
			$year7 +=1900;
			$mon7++;
			$openfile = sprintf("%04d%02d%02d",$year7,$mon7,$mday7);
			$chkwrite = parseloop($openfile,\@webgroup,$chklocation,\@table,$chkwrite);
			if (!$fist_name) {
				$fist_name = $openfile;
			}
		}
		$csvFileName = buildCsvFileName($fist_name, $openfile, $csvFileName);
	}
	elsif ($logdays eq "PREVWEEK" || $logdays eq "CUSTOM") {
		my $first_name = undef;
		foreach my $date (@range_dates) {
			$chkwrite = parseloop($date,\@webgroup,$chklocation,\@table,$chkwrite);
			$first_name ||= $date;
			$openfile = $date;
		}
		$csvFileName = buildCsvFileName($first_name, $openfile, $csvFileName);
	}
	elsif ($logdays eq "THISMONTH") {
		my $first_name = sprintf("%04d%02d",$year,$mon);
		my $tmfname ="access_log.$first_name\\d\\d";

		foreach my $line (@dirfile) {
			if($line =~ /$tmfname$/) {
				my @line = split(/\./,$line);
				$chkwrite = parseloop($line[1],\@webgroup,$chklocation,\@table,$chkwrite);
			}
		}
		my $today = sprintf("%04d%02d%02d",$year,$mon,$mday);
		$csvFileName = buildCsvFileName($first_name . "01", $today, $csvFileName);
	}
	elsif ($logdays eq "LASTMONTH") {
		my $lastsec = timelocal(0,0,0,1,$mon - 1,$year) - 24*3600;
		my ($lsec,$lmin,$lhour,$lmday,$lmon,$lyear,$lwday,$lyday,$lisdst) = localtime($lastsec);
		my $lastmonth = sprintf("%04d%02d%02d",$lyear + 1900,$lmon + 1,$lmday);

		$mon--;
		if($mon == 0){
			$mon = 12;
			$year--;
		}
		my $first_name = sprintf("%04d%02d",$year,$mon);
		my $tmfname = "access_log.$first_name\\d\\d";
		foreach my $line (@dirfile){
			if($line =~/$tmfname$/){
				my @line = split(/\./,$line);
				$chkwrite = parseloop($line[1],\@webgroup,$chklocation,\@table,$chkwrite);
			}
		}
		$csvFileName = buildCsvFileName($first_name . "01", $lastmonth, $csvFileName);
	}

	#
	# 出力
	#
	if($type eq "web") {
		outputWeb($showname, \@webgroup, \@table, $ecgid, $logdays, $egid, $type, $rec, $log_from, $log_to);
	}
	else {
		#
		# CSV ダウンロード
		#
		outputCsv(\@table, $csvFileName);
	}
}

#
# read log and parse 
# 
sub parseloop {
	my $filename = $_[0];
	if (!open (IN, "$dir_pass/access_log.$filename")) {
		return;
	}
	my $webgroup_ref = $_[1];
	if (!@{$webgroup_ref}[0]) {
		return;
	}

	my $location = $_[2];
	$location =~ s/^\///g;
	$location = TF::CommonUtils::escapeRegularExpression($location);

	my $table_ref = $_[3];
	my $chkwrite = $_[4];
	while (<IN>) {
		if($_ =~/(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}) - ([^\/\\:\*\?\"<>\|]+) (\[\d{2}\/\D{3}\/\d{4}:\d{2}:\d{2}:\d{2} \+\d{4}\]) "([A-Z]+) \/($location)(\/Group%20Folder\/)(\S+) HTTP\/1\.[0-1]" (\d{1,3}) ([\d-]+) ("[^"]*") ("[^"]*")/){
			my $ip = $1;
			my $id = $2;
			my @time;
			$time[2] = substr($3,1,2);
			my $chkday = $time[2];
			$time[1] = substr($3,4,3);
			my $chkmon = $time[1];
			$time[0] = substr($3,8,4);
			my $chkyear = $time[0];
			$time[3] = substr($3,13,8);
			my $chkhour = substr($3,13,2);
			my $chkmin = substr($3,16,2);
			my $chksec = substr($3,19,2);
			$chkmon = getmonth($chkmon);
			my $httpstats = $8;
			my $fsize = $9;
			my $usragent = $10;
# (note) 将来サポート予定
#			my $destination = $11;
#			my	($dstpass,$dstfile) = "";
#			if($destination ne "\"-\""){
#				($dstpass,$dstfile) = getdst($destination);
#			}
			
			$time[1] = getmonth($time[1]);
			my $tmps = "$time[0]\/"."$time[1]\/";
			my $tmpss = "$time[2] "."$time[3]";
			my $time = $tmps.$tmpss;
			my $chk=0;
			my($groupname,$parent_uri,$fname,$method) = parseUri($7);
			$groupname = URLd($groupname);
			$groupname = TF::CommonUtils::decodeUTF8($groupname);	
			my $webg = '';
			foreach (@{$webgroup_ref}) {
				if($_->{compname} eq $groupname || $_->{id} eq $groupname) {
					$webg = $_;
					$chk =0;
					last;
				}
				else{
					$chk =1;
				}
			}
			if($chk ==1){
				next;
			}
			# メソッドがGET で WEBDAV_METHOD=PROPFIND であれば表示の必要なし
			if ($4 eq "GET" && $method eq "PROPFIND") {
				next;
			}

			# POST 以外のメソッドではWEBDAV_METHODの値を利用しないのでリクエストメソッドで置き換え
			if($4 ne 'POST') {
				$method = $4;
			}
			my	($Succes,$right,$methodname) = rightorwrong($method,$httpstats);

			# (note)
			# 	MKCOL, LOCK, UNLOCK, COPY, MOVE は書き込みオペレーションですが
			# 	初版では表示されないことになりました.
			#
			if (($method eq "PUT" || $method eq "GET" || $method eq "DELETE") && ($id ne '-')) {

				my $epochtime = timelocal($chksec,$chkmin,$chkhour,$chkday,$chkmon - 1,$chkyear - 1900);
				# ログ日付がグループの作成日付よりも古ければ破棄する (外部仕様です)
				if ($epochtime <= $webg->{creationdt}) {
					next;
				}

				my %tmp;
				$chkwrite = 1;
				$tmp{IP} = $ip;
				$tmp{ID} = $id;
				$tmp{TIME} = $time;
				$tmp{METHOD} = $methodname;
				if ($parent_uri) {
					$parent_uri = URLd($parent_uri);
					$parent_uri = TF::CommonUtils::decodeUTF8($parent_uri);
					$parent_uri = $webg->{compname} . "/" . $parent_uri;
				}
				else {
					$parent_uri = $webg->{compname};
				}
				$tmp{FOLDER} = $parent_uri;
				$fname = URLd($fname);
				$tmp{FILE} = TF::CommonUtils::decodeUTF8($fname);
				$tmp{RorW} = $right;

# (note) 将来サポート予定
#				$dstpass = $webg . "/"."$dstpass";
#				$dstpass = URLd($dstpass);
#				$tmp{dstpass} = $dstpass;
				$tmp{dstpass} = "";
#				$dstfile = URLd($dstfile);
#				$tmp{dstfile} = $dstfile;
				$tmp{dstfile} = "";
				push(@{$table_ref}, \%tmp);	
			}
		}
	}
	close(IN);
	return $chkwrite;
}

#
# get right and output method 
#
sub rightorwrong {
	my $Succes;
	my $right;
	my ($method,$httpstats) =@_;
	if($method eq 'GET'){
		$method = "$MSGTABLE->{mMethodName1}";
		if($httpstats == 200){
			$Succes = "OK";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif ($httpstats == 206){
			$Succes = "Partial Content";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif ($httpstats == 304) {
			$Succes = "Not modified";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'PUT'){
		$method = "$MSGTABLE->{mMethodName2}";
		if($httpstats == 201){
			$Succes = "Created";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif($httpstats ==204){
			$Succes = "No Content";
			$right  = "$MSGTABLE->{mRight}";
		}
		elsif($httpstats == 200){
			$Succes = "OK";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'MKCOL'){
		$method = "$MSGTABLE->{mMethodName3}";
		if($httpstats == 201){
			$Succes = "Created";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif($httpstats == 200){
			$Succes = "OK";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'POST'){
		if($httpstats ==200){
			$Succes ="OK";
		}
		else{
			$Succes ="failed";
		}
	}
	elsif($method eq 'DELETE'){
		$method = "$MSGTABLE->{mMethodName4}";
		if($httpstats == 204){
			$Succes = "No Content";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif($httpstats == 404){
			$Succes = "Not Found";
			$right = "$MSGTABLE->{mRight}";
		}
		elsif($httpstats == 200){
			$Succes = "";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		
		}
	}
	elsif($method eq 'MOVE'){
		$method = "$MSGTABLE->{mMethodName5}";
		if($httpstats == 201){
			$Succes = "Created";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'COPY'){
		$method = "$MSGTABLE->{mMethodName7}";
		if($httpstats == 201){
			$Succes = "Created";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'LOCK'){
		$method = "$MSGTABLE->{mMethodName8}";
		if($httpstats == 200){
			$Succes = "OK";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	elsif($method eq 'UNLOCK'){
		$method = "$MSGTABLE->{mMethodName9}";
		if($httpstats == 204){
			$Succes = "No Content";
			$right = "$MSGTABLE->{mRight}";
		}
		else{
			$Succes = "failed";
			$right = "$MSGTABLE->{mWrong}";
		}
	}
	return ($Succes,$right,$method);
}

#
# parse log
#
sub parseUri {
	my $uri = shift || '';
	my $groupname = '';
	my $parent_uri = '';
	my $fname = '';
	my $method;

	my ($uri_part, $uparam_str) = split(/\?/, $uri);

	# parse URL parameters
	my %uparam_h = parseUriParams($uparam_str);

	# remove double-slash and end-slash
	$uri_part =~ s/[\/]+/\//g;
	$uri_part =~ s/\/$//g;

	# parse URI
	$method = $uparam_h{'WEBDAV_METHOD'} || '';
	if($method eq 'PUT'){
		if ($uri_part =~ /^([^\/]+)\/*(.*)$/) {
			$groupname = $1;
			$parent_uri = $2;
			$fname = $uparam_h{'name'} || '';
		}
	}
	else{
		if($uri_part =~ /^([^\/]+)\/([^\/]+)$/) {
			$groupname = $1;
			$parent_uri = "";
			$fname = $2;
		}
		elsif ($uri_part =~ /^([^\/]+)\/(.*)\/([^\/]+)$/) {
			$groupname = $1;
			$parent_uri = $2;
			$fname = $3;
		}
	}

	return ($groupname,$parent_uri,$fname,$method);
}

#
# get groupname,parent_uri,fname
#
sub parseUriParams {
	my $param_str = shift || undef;
	my %uparam_h ;
	if($param_str){
		my@uparams = split (/&/,$param_str);

		for my $keyval (@uparams){
			my ($key,$val) = split(/=/,$keyval);
			$uparam_h{$key} = $val;
		}
	}
	return %uparam_h;
}

#
# get month
#
sub getmonth {
	my $mon;
	my $data = $_[0];
	if($data eq 'Jan'){
		$mon = "01";
	}
	elsif($data eq 'Feb'){
		$mon = "02";
	}
	elsif($data eq 'Mar'){
		$mon = "03";
	}
	elsif($data eq 'Apr'){
		$mon = "04";
	}
	elsif($data eq 'May'){
		$mon = "05";
	}
	elsif($data eq 'Jun'){
		$mon = "06";
	}
	elsif($data eq 'Jul'){
		$mon = "07";
	}
	elsif($data eq 'Aug'){
		$mon = "08";
	}
	elsif($data eq 'Sep'){
		$mon = "09";
	}
	elsif($data eq 'Oct'){
		$mon = "10";
	}
	elsif($data eq 'Nov'){
		$mon = "11";
	}
	elsif($data eq 'Dec'){
		$mon = "12";
	}
	else{
	}
	return $mon;
}

#
# get destination
#
sub getdst {
	if($_[0] =~ /"http[s]?:\/\/[\w%-_:]+\/[\w%-_]+\/Group%20Folder\/([[\w%-_]+\/_]*)\/([\w%-_]+)"/){
	}
	elsif($_[0]=~ /"http[s]?:\/\/[\w%-_:]+\/[\w%-_]+\/([[\w%-_]+\/?]*)\/([\w%-_]+)"/){
	}
	else{
	}
	return ($1,$2);
}

#
# URL decode 
#
sub URLd {
	my $URLdecode = $_[0];
	$URLdecode =~tr/+/ /;
	$URLdecode =~s/%([a-fA-F0-9]{2})/pack("C",hex($1))/eg;
	return $URLdecode;
}

#
# Parse CGI parameters
#
sub parseCgiParams {
	my $cgi = new CGI;
	if( ! $cgi ) {
		TF::CommonUtils::showErrorPage($MSGTABLE->{CGIError}, $LANGPARAM);
		print STDERR "CGI Object error";
		return undef;
	}

	if( $cgi->cgi_error ) {
		TF::CommonUtils::showErrorPage($MSGTABLE->{CGIError} . '(' . $cgi->cgi_error . ')', $LANGPARAM);
		print STDERR "CGI Error (" . $cgi->cgi->error . ")";
		return undef;
	}

	# URLパラメータの取得
	my $cmd      = TF::CommonUtils::escapeHTML(scalar $cgi->param('hidCmd'), 0);
	my $type     = TF::CommonUtils::escapeHTML(scalar $cgi->param('hidType'), 0);
	my $egid     = TF::CommonUtils::escapeHTML(scalar $cgi->param('hidEgid'), 0);
	my $logdays  = TF::CommonUtils::escapeHTML(scalar $cgi->param('selLogRange'), 0);
	my $ecgid    = TF::CommonUtils::escapeHTML(scalar $cgi->param('hidEcgid'), 0);
	my $rec      = TF::CommonUtils::escapeHTML(scalar $cgi->param('recursive'), 0);
	my $log_from = TF::CommonUtils::escapeHTML(scalar($cgi->param('logFrom') || ''), 0);
	my $log_to   = TF::CommonUtils::escapeHTML(scalar($cgi->param('logTo') || ''), 0);

	# パラメータのvalidation
	if (!$cmd) {
		# コマンドパラメータは必須
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrNoCmd}, $LANGPARAM);
		return undef;
	}
	if ($cmd ne 'alldown' && $cmd ne 'down' && $cmd ne 'locdown') {
		# alldown, down, locdown 以外はNG
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrInvalidCmd}, $LANGPARAM);
		return undef;
	}

	if (!$type || ($type ne 'csv' && $type ne 'web')) {
		# csv, web 以外はNG
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrInvalidType}, $LANGPARAM);
		return undef;
	}
	if ($cmd eq 'alldown' && $type eq 'web') {
		# 組み合わせが正しくない
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrInvalidType}, $LANGPARAM);
		return undef;
	}
	if ($cmd eq 'locdown' && $type ne 'csv') {
		# ロケーション全体の取得はCSVのみ
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrInvalidType}, $LANGPARAM);
		return undef;
	}

	if (!$egid && $cmd eq 'down') {
		# 1グループの操作ログ取得ではグループIDは必須
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrTargetGroup}, $LANGPARAM);
		return undef;
	}
	$egid  = TF::CommonUtils::decodeUTF8($egid);
	$ecgid = TF::CommonUtils::decodeUTF8($ecgid);

	if (!$logdays) {
		# 抽出範囲は必須
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
		return undef;
	}
	if ($logdays ne "TODAY" && $logdays ne "YESTERDAY" && $logdays ne "LASTWEEK" && $logdays ne "PREVWEEK" && $logdays ne "THISMONTH" && $logdays ne "LASTMONTH" && $logdays ne "CUSTOM") {
		# 抽出範囲は必須
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
		return undef;
	}
	if ($logdays eq 'CUSTOM') {
		if ($log_from !~ /^\d{4}-\d{2}-\d{2}$/ || $log_to !~ /^\d{4}-\d{2}-\d{2}$/) {
			TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
			return undef;
		}
		$log_from =~ s/-//g;
		$log_to =~ s/-//g;
		if ($log_from gt $log_to) {
			TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
			return undef;
		}
	}

	return ($cmd, $type, $egid, $logdays, $ecgid ,$rec, $log_from, $log_to);
}

#
# Download all indexed operation logs in current location.
#
sub downloadLocationLog {
	my $type = shift || '';
	my $logdays = shift || '';
	my $log_from = shift || '';
	my $log_to = shift || '';

	if ($USERTYPE ne 'admin') {
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrAccessDeny}, $LANGPARAM);
		return;
	}
	if ($type ne 'csv') {
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrInvalidType}, $LANGPARAM);
		return;
	}

	my @dates = TF::OplogIndex::date_range($logdays, $log_from, $log_to);
	if (!@dates) {
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrRangeParam}, $LANGPARAM);
		return;
	}

	if (!ensureLocationIndexesReady(@dates)) {
		TF::CommonUtils::showErrorPage($MSGTABLE->{mErrIndexNotReady}, $LANGPARAM);
		return;
	}

	my @table = ();
	foreach my $date (@dates) {
		foreach my $row (readLocationRowsByDate($date)) {
			pushIndexRow(\@table, $row);
		}
	}

	my $csvFileName = buildCsvFileName($dates[0], $dates[$#dates], "oplog.csv");
	outputCsv(\@table, $csvFileName);
}

sub ensureLocationIndexesReady {
	foreach my $date (@_) {
		next if isTodayDate($date);
		return 0 if !TF::OplogIndex::is_day_complete($dir_pass, $date);
	}
	return 1;
}

sub readLocationRowsByDate {
	my $date = shift || '';
	if (isTodayDate($date)) {
		return readLocationAccessLog($date);
	}

	my @rows = ();
	foreach my $file (TF::OplogIndex::list_indexed_group_files($dir_pass, $date, $LOCATION)) {
		push(@rows, TF::OplogIndex::read_group_file($file));
	}
	return @rows;
}

sub isTodayDate {
	my $date = shift || '';
	my @today = TF::OplogIndex::date_range('TODAY');
	return ($date && @today && $date eq $today[0]) ? 1 : 0;
}

sub readLocationAccessLog {
	my $date = shift || '';
	my @rows = ();
	my $file = "$dir_pass/access_log.$date";
	my $target_location = TF::OplogIndex::normalize_location($LOCATION);
	my $fh;

	if (!open($fh, '<', $file)) {
		return @rows;
	}

	while (my $line = <$fh>) {
		next if index($line, '/Group%20Folder/') < 0;
		my $row = TF::OplogIndex::parse_access_log_line($line);
		next if !$row;
		next if TF::OplogIndex::normalize_location($row->{location}) ne $target_location;
		push(@rows, $row);
	}
	close($fh);

	return @rows;
}

#
# Convert index row to CGI output row.
#
sub pushIndexRow {
	my $table_ref = shift || return;
	my $row = shift || return;

	my (undef, $right, $methodname) = rightorwrong($row->{method}, $row->{status});
	my %tmp;
	$tmp{IP} = $row->{ip};
	$tmp{ID} = $row->{user};
	$tmp{TIME} = $row->{time};
	$tmp{METHOD} = $methodname;
	$tmp{FOLDER} = $row->{folder};
	$tmp{FILE} = $row->{file};
	$tmp{RorW} = $right;
	$tmp{dstpass} = "";
	$tmp{dstfile} = "";
	push(@{$table_ref}, \%tmp);
}

#
# Output CSV.
#
sub outputCsv {
	my $table_ref = shift || [];
	my $csvFileName = shift || "oplog.csv";
	my $found = 0;

	foreach my $line2 (@{$table_ref}) {
		if (!$found) {
			binmode(STDOUT,":utf8");
			print "Content-type:application/octet-stream\r\n";
			print "Content-Disposition:attachment; filename=$csvFileName\n\n";
			print "\x{FEFF}";
			$found = 1;
		}

		my $folder = csvEscape($line2->{'FOLDER'});
		my $dstpass = csvEscape($line2->{'dstpass'});
		my $file = csvEscape($line2->{'FILE'});
		my $dstfile = csvEscape($line2->{'dstfile'});

		print "$line2->{'IP'},$line2->{'ID'},$line2->{'TIME'},$line2->{'METHOD'},\"$folder\",\"$dstpass\",\"$file\",\"$dstfile\",$line2->{'RorW'}\r\n";
	}

	if ($found == 0){
		TF::CommonUtils::showMessagePage(   $MSGTABLE->{mErrNoLogEntry},
											$LANGPARAM,
											$MSGTABLE->{mErrNoLogEntryT});
		return;
	}
}

sub csvEscape {
	my $value = shift || '';
	$value =~ s/\"/\"\"/g;
	return $value;
}

#
# Read indexed logs for permitted groups.
#
sub readIndexedGroupLogs {
	my $logdays = shift || '';
	my $log_from = shift || '';
	my $log_to = shift || '';
	my $webgroup_ref = shift || [];
	my $table_ref = shift || [];
	my @dates = TF::OplogIndex::date_range($logdays, $log_from, $log_to);

	return 0 if !@dates || !@{$webgroup_ref};

	foreach my $date (@dates) {
		return 0 if !TF::OplogIndex::is_day_complete($dir_pass, $date);
	}

	foreach my $date (@dates) {
		foreach my $group (@{$webgroup_ref}) {
			my $file = TF::OplogIndex::group_file($dir_pass, $date, $LOCATION, $group->{compname});
			next if !-f $file;

			foreach my $row (TF::OplogIndex::read_group_file($file)) {
				if ($group->{creationdt} && $row->{epoch} <= $group->{creationdt}) {
					next;
				}
				pushIndexRow($table_ref, $row);
			}
		}
	}

	return 1;
}

#
# Output Web page.
#
sub outputWeb {
	my ($showname, $webgroup_ref, $table_ref, $ecgid, $logdays, $egid, $type, $rec, $log_from, $log_to) = @_;

	if (!open(TEMPLATE,"<$TEMPLATE_FILE")) {
		TF::CommonUtils::showTemplateOpemErr($LANGPARAM);
		return;
	}
	my $group = $webgroup_ref->[0];
	binmode(TEMPLATE,":utf8");
	my $htmltmpl = HTML::Template->new( filehandle => *TEMPLATE,
										case_sensitive => 1,
										die_on_bad_params => 0);
	if ($showname){
		$showname = TF::CommonUtils::decodeUTF8($showname);
		$htmltmpl->param(	brandname => $BRAND,
							pagetitle => $PAGETITLE,
							pageimg	=> $PAGEIMG,
							LOOP_LOG => $table_ref,
							GROUPNAME => $showname,
							ecgid     => $ecgid,
							logdays   => $logdays,
							logFrom   => formatDateInput($log_from),
							logTo     => formatDateInput($log_to),
							egid	  => $egid,
							type	  => $type,
							REC		  => $rec,
							USR		  => $USERTYPE,
							);
	}
	else {
		$group->{name} = TF::CommonUtils::decodeUTF8($group->{name});
		$htmltmpl->param(	brandname => $BRAND,
							pagetitle => $PAGETITLE,
							pageimg	=> $PAGEIMG,
							LOOP_LOG => $table_ref,
							GROUPNAME => $group->{name},
							ecgid     => $ecgid,
							logdays   => $logdays,
							logFrom   => formatDateInput($log_from),
							logTo     => formatDateInput($log_to),
							egid	  => $egid,
							type	  => $type,
							REC		  => $rec,
							USR		  => $USERTYPE,
						);
	}
	print "Content-type: text/html; charset=UTF-8\n\n";
	print $htmltmpl->output;

	close(TEMPLATE);
}

sub formatDateInput {
	my $date = shift || '';
	if ($date =~ /^(\d{4})(\d{2})(\d{2})$/) {
		return "$1-$2-$3";
	}
	return $date;
}

#
# Get groupnames from TeamFile server
#
# [ Use environment value ]
# 	$URL, $USERID, $PASSWD, $USERTYPE
#
sub getGroupNames {

	my $gid   = shift || '';
	my $isAll = shift || 0;
	my $rec   = shift || '';
	my @groupnames = ();

	my $glist = TF::GroupList->new();
	my $queryGroupStatus = undef;
	my $isAdmin = undef;
	my $lcname = $LOCATION;
	$lcname =~ s/\///g;
	
	# グループ制約適用の有無を取得
	my $use_grpconstraints = 1;
	if ($USERPRIVILEGE =~ /group-constraints-ignore/) {
		$use_grpconstraints = 0;
	}
	my $showname = "";

	#
	# グループ一覧を取得
	#
	#
	# 管理者 の場合
	#
	if ($USERTYPE eq 'admin') {
		if ($isAll && $rec ne 'on') {
			# まとめてダウンロードであれば、全グループのリストを取得
			#再帰的に取得しない場合

			$queryGroupStatus = $glist->queryGroupList($URL,$USERID,$PASSWD,$gid);
			if($queryGroupStatus == 207 ) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);

					# $gid 以外のグループを記録する
					if ($gid && $_ eq $gid) {
						next;
					}
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
				}
			}
		}
		elsif ($isAll && $rec eq 'on') {
			#まとめてダウンロードかつ、再帰的に取得する場合、全てのグループを取得
			$queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
			if ($queryGroupStatus == 207) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);
					my $child = $glist->getChildCount($_);

					if($gid && $_ eq $gid) {
						next;
					}
					push( @groupnames,{ compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
					
					});
					if ($child && $child > 0) {
						getRecursive($URL,$USERID,$PASSWD,$gid,$lcname,\@groupnames);
					}
				}
			}
		}
		elsif (!$isAll && $rec ne 'on') {
			#個別に取得
			$queryGroupStatus = $glist->queryGroup($URL,$USERID,$PASSWD,$gid);
			if ($queryGroupStatus == 207) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);
					# (note) queryGroup なので戻ってくるグループは1つだけ 
					$showname = $name;
					push ( @groupnames,{ compname => $str,
										 name => $name,
										 id => $_,
										 creationdt => $glist->getGroupCreationDt($_)
					});
				}
			}
		}


		
		elsif (!$isAll && $rec eq 'on') {
			$queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
			if ($queryGroupStatus == 207) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					my $child = $glist->getChildCount($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);
					if ($gid && $gid eq $_) {
						$showname = $name;
					}
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
					if($child && $child > 0) {
						getRecursive($URL,$USERID,$PASSWD,$gid,$lcname,\@groupnames);		
					}	
				}
			}
		}
	}
	#
	# グループリーダの場合
	#
	elsif ($USERTYPE eq 'groupleader') {
		# まとめてダウンロードの場合
		if ($isAll && $rec ne 'on') {
			# まとめてダウンロードであれば、管理グループのリストを取得

			$queryGroupStatus = $glist->queryGroupList($URL,$USERID,$PASSWD,$gid);
			if($queryGroupStatus == 207 ) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					utf8::encode($name);

					# $gid 以外のグループを記録する
					if ($gid && $_ eq $gid) {
						next;
					}
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
				}
			}

			# トップフォルダの検索では所属グループも取得する
			if (!$gid) {
				my $glist2 = TF::GroupList->new();
				my $queryGroupStatus2 = $glist2->queryAvailableGroupList($URL,$USERID,$PASSWD);
				if($queryGroupStatus2 == 207 ) {

					foreach ($glist2->getIDList()) {
						# 操作ログ制約によりブロックされていればスキップ
						if ($glist2->hasOplogConstraints($_) && $use_grpconstraints) {
							next;
						}
						# 非アクティブグループは表示しないグループリーダの場合
						if (!$glist2->isActive($_)) {
							next;
						}
						my $str = $glist2->getGroupFolderName($_,$lcname);
						my $name = $glist2->getName($_);
						utf8::encode($name);
						$str = URLd($str);
						$str = TF::CommonUtils::decodeUTF8($str);

						# まとめてダウンロードの場合にはそのまま記録する
						push( @groupnames,{	compname => $str,
											name => $name,
											id => $_,
											creationdt => $glist2->getGroupCreationDt($_)
											});
					}
				}
			}
		}
		elsif($isAll && $rec eq 'on') {
		# まとめてダウンロードであれば、管理グループのリストを取得

			$queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
			if($queryGroupStatus == 207 ) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					my $child = $glist->getChildCount($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);

					# $gid 以外のグループを記録する
					if ($gid && $_ eq $gid) {
						next;
					}
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
					if ($child && $child > 0) {
						getRecursive($URL,$USERID,$PASSWD,$gid,$lcname,\@groupnames);
					}
				}
			}

			# トップフォルダの検索では所属グループも取得する
			if (!$gid) {
				my $glist2 = TF::GroupList->new();
				my $queryGroupStatus2 = $glist2->queryAvailableGroupList($URL,$USERID,$PASSWD);
				if($queryGroupStatus2 == 207 ) {

					foreach ($glist2->getIDList()) {
						# 操作ログ制約によりブロックされていればスキップ
						if ($glist2->hasOplogConstraints($_) && $use_grpconstraints) {
							next;
						}
						# 非アクティブグループは表示しないグループリーダの場合
						if (!$glist2->isActive($_)) {
							next;
						}
						my $str = $glist2->getGroupFolderName($_,$lcname);
						my $name = $glist2->getName($_);
						utf8::encode($name);
						$str = URLd($str);
						$str = TF::CommonUtils::decodeUTF8($str);

						# まとめてダウンロードの場合にはそのまま記録する
						push( @groupnames,{	compname => $str,
											name => $name,
											id => $_,
											creationdt => $glist2->getGroupCreationDt($_)
											});
					}
				}
			}	
		}
		# 個別ダウンロードの場合
		elsif (!$isAll && $rec ne 'on') {
			my $search_gid;
			# $gid のグループだけを検索する
			$queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
			if ($queryGroupStatus == 207) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);
					# (note) queryGroup なので戻ってくるグループは1つだけ 
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
					$search_gid = $_;
					last;
				}
			}

			# 上記で取得できなかったら、所属グループも検索してみる
			if (!$search_gid) {
				my $glist2 = TF::GroupList->new();
				my $queryGroupStatus2 = $glist2->queryAvailableGroupList($URL,$USERID,$PASSWD);
				if($queryGroupStatus2 == 207 ) {

					foreach ($glist2->getIDList()) {
						# 操作ログ制約によりブロックされていればスキップ
						if ($glist2->hasOplogConstraints($_) && $use_grpconstraints) {
							next;
						}
						# 非アクティブグループは表示しない
						if (!$glist2->isActive($_)) {
							next;
						}

						my $str  = $glist2->getGroupFolderName($_,$lcname);
						my $name = $glist2->getName($_);
						utf8::encode($name);
						$str = URLd($str);
						$str = TF::CommonUtils::decodeUTF8($str);					
						if ($_ eq $gid) {
							push( @groupnames,{	compname => $str,
												name => $name,
												id => $_,
												creationdt => $glist2->getGroupCreationDt($_)
											});
							last;
						}
						# それ以外は無視
					}
				}
			}
		}
	
	elsif(!$isAll && $rec eq 'on'){
			my $search_gid;
			# $gid のグループだけを検索する
			$queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
			if ($queryGroupStatus == 207) {
				foreach ($glist->getIDList()) {
					my $str = $glist->getGroupFolderName($_,$lcname);
					my $name = $glist->getName($_);
					my $child = $glist->getChildCount($_);
					utf8::encode($name);
					$str = URLd($str);
					$str = TF::CommonUtils::decodeUTF8($str);

					# (note) queryGroup なので戻ってくるグループは1つだけ 
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
					$search_gid = $_;
					if ($child && $child > 0) {
						getRecursive($URL,$USERID,$PASSWD,$gid,$lcname,\@groupnames);
					}
	#				last;
				}
			}

			# 上記で取得できなかったら、所属グループも検索してみる
			if (!$search_gid) {
				my $glist2 = TF::GroupList->new();
				my $queryGroupStatus2 = $glist2->queryAvailableGroupList($URL,$USERID,$PASSWD);
				if($queryGroupStatus2 == 207 ) {

					foreach ($glist2->getIDList()) {
						# 操作ログ制約によりブロックされていればスキップ
						if ($glist2->hasOplogConstraints($_) && $use_grpconstraints) {
							next;
						}
						# 非アクティブグループは表示しない
						if (!$glist2->isActive($_)) {
							next;
						}

						my $str  = $glist2->getGroupFolderName($_,$lcname);
						my $name = $glist2->getName($_);
						utf8::encode($name);
						$str = URLd($str);
						$str = TF::CommonUtils::decodeUTF8($str);
						
						if ($_ eq $gid) {
							push( @groupnames,{	compname => $str,
												name => $name,
												id => $_,
												creationdt => $glist2->getGroupCreationDt($_)
											});
							last;
						}
						# それ以外は無視
					}
				}
			}
		}
	}
	
	#
	# 一般ユーザの場合
	#
	else {
		$queryGroupStatus = $glist->queryAvailableGroupList($URL,$USERID,$PASSWD);
		if($queryGroupStatus == 207 ) {

			foreach ($glist->getIDList()) {
				# 操作ログ制約によりブロックされていればスキップ
				if ($glist->hasOplogConstraints($_) && $use_grpconstraints) {
					next;
				}
				# 非アクティブグループは表示しない (一般ユーザの場合)
				if (!$glist->isActive($_)) {
					next;
				}

				my $str = $glist->getGroupFolderName($_,$lcname);
				my $name = $glist->getName($_);
				utf8::encode($name);
				$str = URLd($str);
				$str = TF::CommonUtils::decodeUTF8($str);
				if ($isAll) {
					# まとめてダウンロードの場合にはそのまま記録する
					push( @groupnames,{	compname => $str,
										name => $name,
										id => $_,
										creationdt => $glist->getGroupCreationDt($_)
									});
				}
				else {
					# 通常ダウンロードでは指定された見つかった(=所属していた)グループのみを記録する
					if ($_ eq $gid) {
						push( @groupnames,{	compname => $str,
											name => $name,
											id => $_,
											creationdt => $glist->getGroupCreationDt($_)
										});
						last;
					}
					# それ以外は無視
				}
			}
		}
	}

	return ($showname,@groupnames);
}

#
# Build CSV filename
#
sub buildCsvFileName {
	my ($from, $to, $suffix) = @_;

	return $from . "-" . $to . "_" . $suffix;
}

sub getRecursive {
	my $URL = shift || '';
	my $USERID = shift || '';
	my $PASSWD = shift || '';
	my $gid = shift || '';
	my $lcname = shift || '';
	my $groupnames = shift || '';
	my $glist = TF::GroupList->new();	
	my $queryGroupStatus = $glist->queryGroupTreeList($URL,$USERID,$PASSWD,$gid);
	if ($queryGroupStatus == 207) {
		foreach ($glist->getIDList()) {
			if ($gid eq $_){
				next;
			}
			my $str = $glist->getGroupFolderName($_,$lcname);
			my $name = $glist->getName($_);
			my $child = $glist->getChildCount($_);
		#	utf8::encode($name);
			push( @$groupnames,{ 		compname => $str,
								 name => $name,
								 id => $_,
								 creationdt => $glist->getGroupCreationDt($_)
								});
			if($child && $child > 0) {
				getRecursive($URL,$USERID,$PASSWD,$_,$lcname,$groupnames);
			}
		}
	}
}
