#!/usr/bin/perl -w
# This script parses Asterisk's call data records CSV file and outputs 
# as an HTML page.  Adapted from a script found on archive.org 
# originally by http://www.3ait.co.uk and Adam Pankov
#
# Copyright 2009 James Stocks
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# If not, see <http://www.gnu.org/licenses/>.
#
# 2009-11-03 by James Stocks http://www.stocksy.co.uk
#
#   These are the fields in the CDR CSV file
#   The digit to the side is the index in @cdrentry
#
# 0  "accountcode",            // Sets the Account Code for this call (if present)
# 1  "source",                 // The phone number that originated the call 
# 2  "destination",            // The phone number that was dialled 
# 3  "destination context",    // Context in which the call terminated
# 4  "callerid",               // The CallerID of the caller (if available)
# 5  "channel",                // What channel, if any, was used to connect the call
# 6  "destination channel" ,   // As above 
# 7  "last application",       // Last application run on the channel
# 8  "last app argument",      // Arguments passed to the above mentioned application 
# 9  "start time",             // The time at which the call was placed 
# 10 "answer time",  	       // The time at which the call was answered
# 11 "end time",	           // The time at which the call was cleared down 
# 12 duration,		           // Duration of the call including ringtime etc 
# 13 billable seconds,	       // How much calltime was billable (e.g. not ring time)
# 14 "disposition",            // What was the call  result - ANSWERED, NO ANSWER, BUSY
# 15 "amaflags",               // DOCUMENTATION, BILL, IGNORE etc - usually not set 
# 16 "uniqueid",               // Unique ID not usually useful unless recording calls 
# 17 "userfield"               // User field set in SetCDRUserField 

# Perl's idea of printing dates/times with localtime() is horrific
use POSIX qw(strftime);

# Don't cache this page
print "Expires: Sunday, 31-Jul-1983 22:44:00 GMT\r\n"; 
# It's HTML of course
print "Content-Type: text/html\r\n\r\n";

# Which file are we reading?
# Make sure it is readable by your apache user (www-data on debian)
#my $cdrfile = "/var/log/asterisk/cdr-csv/Master.csv";
my $cdrfile = "/usr/lib/cgi-bin/calls/Master.csv";
# Do we store all recordings of calls?
my $hasrecord = 0; 
# If yes, where are they? This has to be a URL
my $recordings = "http://your.server.tld/path";

# If there's no CDR file, then there's no poing going further
open (CDRLOG, "<$cdrfile") || doerror();

#If the amaflags are NOT set to OMIT the calls from the CDR, add it to the array to be processed.
while (<CDRLOG>) { # Load the contents of the file into an array
	if (! (/$\"OMIT\"/)) { 
		push (@temp, $_); 
	}
}

parseargs ($ENV{QUERY_STRING}); # Parse the arguments to this script

# If the user has set a number of records to be displayed use this number of records per page, else use 200
if ($form{numrecords} > 0) {		 
	$numrecords = $form{numrecords}; 
} else {
	$numrecords = 200;
}

# For older/newer buttons
if ($form{offset} > 0) {		 
	$offset = $form{offset};
} else {
	$offset = 0;
}

if (($offset + $numrecords) > (scalar @temp)) {
        $numrecords = ((scalar @temp) - $offset);
}

if ($offset >= (scalar @temp)) {
	$offset = 0;
}

# Print the page head 
openpage(); 

# Build the table
print "<table class=\"tbl\">\n";
print "<tr>\n";
print "<th class=\"tbltitle\">Caller ID</th>\n";
print "<th class=\"tbltitle\">From</th>\n";
print "<th class=\"tbltitle\">To</th>\n";
#print "<th class=\"tbltitle\">Channel</th>\n";
print "<th class=\"tbltitle\">Result</th>\n";
print "<th class=\"tbltitle\">Start Time</th>\n";
print "<th class=\"tbltitle\">End Time</th>\n";
print "<th class=\"tbltitle\">Duration</th>\n";

# Is call recording enabled?
if ($hasrecord) {
	# Yes? Add a Listen column
	print "<th class=\"tbltitle\">Listen</th>\n</tr>";
} else {
	# No? Close the tablerow.
	print "</tr>\n";
}

close (CDRLOG);

# The CDR logfile goes oldestcall->newestcall - reverse this
@records = reverse @temp;

# Print the tablerows
for ($i=$offset;$i<$numrecords + $offset;$i++) { 
	# Replace """ with "
	$records[$i] =~ s/\"\"\"/\"/;     
	# Replace ""<whitespace> with "
	$records[$i] =~ s/\"\" /\" /;     
	# Replace "" with "0" so the next line doesn't break
	$records[$i] =~ s/\"\"/\"0\"/g;   
	# Replace ,, with ,0,
	$records[$i] =~ s/\,\,/\,0\,/;
	# Lose the double-quotes
	$records[$i] =~ s/\"//g;

# The data should now be fairly sane and able to be parsed
	# Stick all the info into an array
	@cdrentry = split (/,/, $records[$i]); 
	
	# Start a new row
	print "<tr>\n";
	
	# Are these fields set to 0? i.e. no CallerID?
	if (($cdrentry[1] ne "0") and ($cdrentry[4] ne "0")) { 
		print "<td class=\"tblcell\">$cdrentry[4]</td>\n";

		# Internal call?
		if ($cdrentry[1] =~ /^2\d\d/) {			
			print "<td class=\"tblcella\">$cdrentry[1]</td>\n";
		} else {
			print "<td class=\"tblcellb\">$cdrentry[1]</td>\n";
		}
	} else {
		print "<td class=\"tblcelld\">Unavailable</td>\n";
		print "<td class=\"tblcelld\">External Caller</td>\n";
	}
	
	# Did this call go to the extension 's'? Probably an incoming call
	if ($cdrentry[2] ne "s") { 
		if ($cdrentry[2] =~ /^9\d*/) {
			print "<td class=\"tblcellb\">$cdrentry[2]</td>\n";
		} else {
			print "<td class=\"tblcella\">$cdrentry[2]</td>\n";
		}
	} else {
		print "<td class=\"tblcellc\">Incoming Call</td>\n";
	}
	# Show which channel the call came through - can be useful
	#print "<td class=\"tblcell\">$cdrentry[6]</td>\n";
	# If call was answered colour it green, else colour it red
	if ($cdrentry[14] ne "ANSWERED") {
		print "<td class=\"tblcelld\">$cdrentry[14]</td>\n";
		} else {
			print "<td class=\"tblcella\">$cdrentry[14]</td>\n";
		}
	print "<td class=\"tblcell\">$cdrentry[9]</td>\n";
	print "<td class=\"tblcell\">$cdrentry[11]</td>\n";

	$time = int($cdrentry[12] / 60);
	$secs = $cdrentry[12] - ($time * 60);
	if ($secs < 10) { $secs = "0".$secs; }
	# Convert duration from seconds to minutes:seconds
	print "<td class=\"tblcell\">$time:$secs</td>\n"; 
	
	# If recordings are enabled, print the 'Listen' column, and link to the .wav
	if ($hasrecord) { 	
		@temp = split (/ /, $cdrentry[9]);
		@sort = split (/-/, $temp[0]);
		$temp[0] = $sort[2] . $sort[1] . $sort[0];
		$temp[1] =~ s/-/:/g;
		$callfilename = "$temp[0]-$temp[1]-$cdrentry[16].wav";
	
		print "<td class=\"tblcell\"><a href='$recordings/$callfilename'>";
		print "Listen</a></td>\n</tr>\n"
	} else {
		# Otherwise, don't include links to non-existant recordings
		print "</tr>\n";
	}
}

# Close the table
print "</table>\n"; 

# Close the <body> and <html> tags
closepage();      

exit 0;

# Call this sub if there's an error somewhere and we need to bail
sub doerror () { 
	# If there was an error, print the error and exit
	$error = $!;
	print "There was an error whilst running this script. The error was: $error.\n";
	exit 255;
}

# Print out the page <head> and the navigation links
sub openpage() {
	$title = "Call Records Report: " . strftime "%A %d/%m/%Y %H:%M:%S", localtime;
	print "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n";
	print "\"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
	print "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n";
	print "<head>\n<title>$title</title>\n";
	print "<style>\n";
	print ".tbl       { margin-left: auto; margin-right: auto; background: #bbbbbb; }\n";
	print ".tbltitle  { font-size: large; font-weight: bold; }\n";
	print ".tblcell   { background: #ffffff; color: #333333; border: 2px solid #ffffff; text-align: center; }\n";   # light grey cells
	print ".tblcella  { background: #bbeebb; color: #003300; border: 2px solid #bbeebb; text-align: center; }\n";   # green cells
	print ".tblcellb  { background: #ccccee; color: #222266; border: 2px solid #ccccee; text-align: center; }\n";   # blue cells
	print ".tblcellc  { background: #eeeeaa; color: #555500; border: 2px solid #eeeeaa; text-align: center; }\n";   # yellow cells
	print ".tblcelld  { background: #eeaaaa; color: #990000; border: 2px solid #eeaaaa; text-align: center; }\n";   # red cells
	print "h1         { text-align: center }\n";
	print "</style>\n";

	print "</head>\n";
	print "<body>\n";
	print "<h1>" . $title . "</h1>\n";
	print "<table align=\"center\">\n";
	print "<tr>\n";
	print "<td>\n";
	print "<a href=\"http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}?numrecords=$numrecords&offset=";
	print $offset - $numrecords;
	print "\">&lt;= Newer records</a>\n";
	print "</td>\n";
	print "<td>\n";
	print "Displaying last $numrecords calls, starting at offset $offset";
	print "</td>\n";
	print "<td>\n";
	print "<a href='http://$ENV{SERVER_NAME}$ENV{SCRIPT_NAME}?numrecords=$numrecords&offset=";
	print $offset + $numrecords;
	print "'>Older records =&gt;</a>\n";
	print "</td>\n";
	print "</tr>\n";
	print "</table>\n";
	print "<br /><br />\n";
}

# Just close the <body> and <html> tags
sub closepage() { 
	print "</body>\n</html>";
}

# Parse the GET request, insert values into %form
# I don't understand how this works but it does work
sub parseargs { 
	$buffer = $_[0];
	@pairs = split(/&/, $buffer);

	foreach $pair (@pairs) {
		($name, $value) = split(/=/, $pair);
		$value =~ tr/+/ /;
		$value =~ s/%([a-fA-F0-9][a-fA-F0-9])/pack("C", hex($1))/eg;
		$form{$name} = $value;
	}
}
