<?php
	
/**
 * AgileBill - Open Billing Software
 *
 * This body of work is free software; you can redistribute it and/or
 * modify it under the terms of the Open AgileBill License
 * License as published at http://www.agileco.com/agilebill/license1-4.txt
 * 
 * For questions, help, comments, discussion, etc., please join the
 * Agileco community forums at http://forum.agileco.com/ 
 *
 * @link http://www.agileco.com/
 * @copyright 2004-2008 Agileco, LLC.
 * @license http://www.agileco.com/agilebill/license1-4.txt
 * @author Tony Landis <tony@agileco.com> and Thralling Penguin, LLC <http://www.thrallingpenguin.com>
 * @package AgileBill
 * @version 1.4.93
 */

class didArea
{
	var $cc;
	var $data;
	
	function didArea($cc = 1, $data = false)
	{
		$this->cc = $cc;
		$this->data = $data;
	}
	
	function determineArea($cc, $number)
	{	
		$db =& DB();	
		switch($cc) {
			case 1:
				/* usa - return the npa,nxx */
				return substr($number,0,6);
			default:
				$sql = "SELECT locName, npa FROM ".AGILE_DB_PREFIX."voip_npa_nxx 
					WHERE country_code=".$db->qstr($cc)." ORDER BY length(npa) desc";
				$rs = $db->Execute($sql);
				if($rs && $rs->RecordCount()) {
					while(!$rs->EOF) {
						if(strncmp($rs->fields['npa'],$number,strlen($rs->fields['npa']))==0) {
							return $rs->fields['npa'];
						}
						$rs->MoveNext();
					}
					return false;
				} else {
					return false;
				}
		}
	}
	
	function getName()
	{
		return $this->data['locName'];
	}
	
	function getState()
	{
		return $this->data['locState'];
	}
	
	function getAreacode()
	{
		return $this->data['areacode'];
	}
	
	function getNpa()
	{
		return $this->data['npa'];
	}
	
	function getNxx()
	{
		return $this->data['nxx'];
	}
	
	function getStations($plugins)
	{
		// $data['areacode'] or $data['npa']+$data['nxx']
		$p = AGILE_DB_PREFIX;
		$db =& DB();
		$pre = "";
		if($this->data['country_code'] == 1) {	
			$sql = "select distinct A.country_code,A.npa,A.nxx,A.station
				FROM {$p}voip_pool AS A
				left join {$p}voip_npa_nxx AS B
				on (A.npa=B.npa and A.nxx=B.nxx AND B.country_code='1') 
				WHERE (A.account_id IS NULL OR A.account_id = 0)
				AND (A.date_reserved IS NULL OR A.date_reserved = 0)	
				AND A.npa = " . $db->qstr($this->data['npa']) . "
				AND A.nxx = " . $db->qstr($this->data['nxx']) . "
				AND A.voip_did_plugin_id in (".join(",",$plugins).")
				AND A.site_id=".DEFAULT_SITE."  
				LIMIT 0,50";  	
		} else {
			$sql = "select distinct A.country_code,A.areacode as npa,A.nxx,A.station
				FROM {$p}voip_pool AS A
				left join {$p}voip_npa_nxx AS B
				on (A.areacode=B.npa AND B.country_code=".$db->qstr($this->data['country_code']).") 
				WHERE (A.account_id IS NULL OR A.account_id = 0)
				AND (A.date_reserved IS NULL OR A.date_reserved = 0)	
				AND A.areacode = " . $db->qstr($this->data['areacode']) . "
				AND A.voip_did_plugin_id in (".join(",",$plugins).")
				AND A.site_id=".DEFAULT_SITE."  
				LIMIT 0,50";
			$pre = "011";  			
		}
		#echo "document.write('".str_replace("'","\\'",str_replace("\n","",$sql))."');"; return;
		$rs = $db->Execute($sql);
		if($rs && $rs->RecordCount()) {
			$i = 0;
			while(!$rs->EOF) {
				if ($rs->fields['country_code'] == '1') {
					$dids[$i][0] = $pre.$rs->fields['country_code'].$rs->fields['npa'].$rs->fields['nxx'].$rs->fields['station'];
					$dids[$i++][1] = $rs->fields['country_code']." ".$rs->fields['npa'].$rs->fields['nxx'].$rs->fields['station'];
				} else {
					$dids[$i][0] = $pre.$rs->fields['country_code'].$rs->fields['station'];
					$dids[$i++][1] = $rs->fields['country_code']." ".$rs->fields['station'];
				}
				$rs->MoveNext();
			}
			return $dids;
		}
		syslog(LOG_INFO,$db->ErrorMsg()." -> ".$sql);
		return false;
	}
}

class didAreas
{
	var $data, $cc;
	
	function didAreas($cc, $plugins = false)
	{
		$this->cc = $cc;
		$p = AGILE_DB_PREFIX;
		$db =& DB();
		if($cc!=1) {
			$sql = "select distinct A.areacode,B.locName 
				from {$p}voip_pool AS A 
				inner join {$p}voip_npa_nxx AS B 
				on (A.areacode=B.npa and A.country_code=".$db->qstr($cc)." AND 
				B.country_code=".$db->qstr($cc).") 
				WHERE (A.account_id IS NULL OR A.account_id = 0) AND 
				(A.date_reserved IS NULL OR A.date_reserved = 0) AND ";
			if(is_array($plugins))
				$sql .= "A.voip_did_plugin_id in (".join(",",$plugins).") AND ";
			$sql .= "A.site_id=".DEFAULT_SITE." ORDER BY B.locName";
		} else {		
			$sql = "select distinct A.npa,A.nxx,B.locName,B.locState from {$p}voip_pool AS A inner join {$p}voip_npa_nxx AS B on
				(A.npa=B.npa and A.nxx=B.nxx and B.country_code='1')
				WHERE (A.account_id IS NULL OR A.account_id = 0) AND 
				(A.date_reserved IS NULL OR A.date_reserved = 0) AND ";	 		
			if(is_array($plugins))
				$sql .= "A.voip_did_plugin_id in (".join(",",$plugins).") AND ";
		 	$sql .= "A.site_id=".DEFAULT_SITE." ORDER BY B.locName";
		}
		# echo "document.write('".str_replace("'","\\'",str_replace("\n","",$sql))."');";	
		$rs = $db->Execute($sql);
		if($rs && $rs->RecordCount()) {
			while(!$rs->EOF) {
				$this->data[] = $rs->fields;
				$rs->MoveNext();
			}
		}
	}
	
	function getArea()
	{
		if(!is_array($this->data)) return false;
		$row = each($this->data);
		if($row === false)	{ reset($this->data); return false; }
		return new didArea($this->cc, $row[1]);
	}
}

class didCountry
{
	var $data;
	
	function didCountry($row)
	{
		$this->data = $row;
	}
	
	function getAreas($plugin = false)
	{
		return new didAreas($this->data['country_code'],$plugin);
	}
	
	function getCode()
	{
		return $this->data['country_code'];
	}
	
	function getName()
	{
		return $this->data['name'];
	}
}

class didCountries
{
	var $data;
	var $did_plugin_ids;
	
	function didCountries($pluginArray)
	{	
		$this->did_plugin_ids = $pluginArray;
		$p = AGILE_DB_PREFIX;
		$db =& DB();
		$sql = "select distinct a.country_code,c.id,c.code,c.name from {$p}voip_pool as b 
				left join {$p}voip_iso_country_code_map as a on (a.country_code=b.country_code) 
				left join {$p}voip_iso_country_code as c on (a.iso_country_code=c.code) 
				where (account_id IS NULL or account_id=0)
				and (b.date_reserved IS NULL or b.date_reserved = 0 )
				and c.site_id=".DEFAULT_SITE." AND b.site_id=".DEFAULT_SITE." and a.site_id=".DEFAULT_SITE;
		$rs = $db->Execute($sql);
		if($rs && $rs->RecordCount()) {	
			while(!$rs->EOF) {	
				$this->data[] = $rs->fields;
				$rs->MoveNext();	
			}
		}
		#echo "<pre>".print_r($this->data,true)."</pre>";
		reset($this->data);
	}
	
	function getCountry()
	{
		$row = each($this->data);
		if($row === false) { reset($this->data); return false; }
		return new didCountry($row[1]);
	}
}


class DateFunc {

	function day_of_week( $m, $d, $y ) {
		// Calculate the day of the week, with sat. starting it
		$r = date('w',mktime(0,0,0,$m,$d,$y));
		// As r stands above, sun=0 sat=6
		$r = $r + 1;
		if( $r > 6 )
		$r = 0;
		// now sun=6 sat=0
		return $r;
	}

	function dow( $m, $d, $y ) {
		$a = $this->day_of_week( $m, $d, $y );
		
		switch( $a ) {
		case 0:
			return "SA";
		case 1:
			return "SU";
		case 2:
			return "MO";
		case 3:
			return "TU";
		case 4:
			return "WE";
		case 5:
			return "TH";
		case 6:
			return "FR";
		}
	}

	function week_of_year( $m, $d, $y ) {
		// Calculate the week of the year, using Saturdays...
		$day = date('z',mktime(0,0,0,$m,$d,$y) ) + 1;
		$wday= $this->day_of_week($m,$d,$y) + 1;
		$week = 0;
		
		while( $day > 0 ) {
			$wday = $wday - 1;
			$day = $day - 1;
			if( $wday < 0 )	{
				$wday = $wday + 7;
				$week = $week + 1;
			}
		}
		return $week;
	}

	function get_range_begin( $m, $d, $y ) {
		// First valid date in the week (always a Sat)
		return mktime(0,0,0,$m,($d+(6-$this->day_of_week($m,$d,$y)))-6,$y);
	}
	
	function get_range_end( $m, $d, $y ) {
		// End valid date in a week (always a Fri)
		return mktime(0,0,0,$m,($d+(6-$this->day_of_week($m,$d,$y))),$y);
	}

	function getWeekArray() {
		$cwn = $this->week_of_year(date('m'),date('d'),date('Y'));
		$ts  = mktime(0,0,0,date('m'),date('d'),date('Y'));
		$n = 0;
		for( $i=$cwn; $n<16; $i-- ) {
			$j = $i;
			if( $j > 52 )
				$j = abs(53 - $j) + 1;
			else if( $j < 1 )
				$j = 53 + $j;
			$ret[$n]['text'] = "Week #$j " . date(UNIX_DATE_FORMAT,$this->get_range_begin(date('m',$ts),date('d',$ts),date('Y',$ts))) . " through " . date(UNIX_DATE_FORMAT,$this->get_range_end(date('m',$ts),date('d',$ts),date('Y',$ts)));
			$ret[$n]['begin'] = $this->get_range_begin(date('m',$ts),date('d',$ts),date('Y',$ts));
			$ret[$n]['end'] = $this->get_range_end(date('m',$ts),date('d',$ts),date('Y',$ts));
			$ret[$n]['week'] = $j;
			
			$n++;
			$ts = $ts - (86400 * 7);
		}
		return $ret;
	}

	function getNumberOfDays($date1, $date2) {
		# Correct parameters if needed.
		if($date2<$date1) {
			$tmp = $date1;
			$date1 = $date2;
			$date2 = $tmp;
		}
		# Get the year, month, day values of the two dates
		#$d1[0] = date('Y',$date1); #substr($date1,0,4);
		#$d1[1] = date('m',$date1); #substr($date1,4,2);
		#$d1[2] = date('d',$date1); #substr($date1,6,2);
		#$d2[0] = date('Y',$date2); #substr($date2,0,4);
		#$d2[1] = date('m',$date2); #substr($date2,4,2);
		#$d2[2] = date('d',$date2); #substr($date2,6,2);

		# Construct UNIX timestamps based on the original dates
		$date1 = mktime(0,0,0, date('m',$date1), date('d',$date1), date('Y',$date1));
		$date2 = mktime(0,0,0, date('m',$date2), date('d',$date2), date('Y',$date2));
		#print "date1=".date('Ymd',$date1)."<br>date2=".date('Ymd',$date2)."<br>"; exit;
		$tmp = $date1;
		$y = date('Y',$date1); 
		$m = date('m',$date1);
		$d = date('d',$date1);
		$days = 0;

		# Increments $tmp one day at a time until it matches $date2
		while ($tmp != $date2) {
			$d++;
			$tmp = mktime(0,0,0, date($m), date($d), date($y));
			$days++;
			#echo "tmp=$tmp date2=$date2 m=$m d=$d y=$y<br>";
			#if($d>30) break;
		} # while

		# Returns the number of increments for the dates to match
		return $days;
	}
}


class voip
{ 	
	var $voip_intrastate;
	var $voip_default_prefix;
	var $perform_normalization;
	var $normalization_min_len;
	 
	/**
	* Handle the click to call feature. This is complex as apache/php runs as wwwadmin, so this may need an additional layer
	* to allow asterisk to receive the call file as owner root.
	*/
	function place_call($VAR)
	{
		global $C_debug;
		
		# gimmie temp file
		$file = tempnam("/tmp","clicktocall-").".call";
		$C_debug->alert("Placing Call, please answer your phone when it rings and the call will then be completed.");
		
		$fp = fopen($file, "w");
		fwrite($fp,"Channel: ".$VAR['voip_from']."\n");
		fwrite($fp,"MaxRetries: 0\n");
		fwrite($fp,"RetryTime: 60\n");
		fwrite($fp,"WaitTime: 20\n");
		fwrite($fp,"Callerid: \"Click to Crash\" <".$VAR['voip_callerid'].">\n");
		fwrite($fp,"Context: international\n");
		fwrite($fp,"Extension: ".$VAR['voip_to']."\n");
		fwrite($fp,"Priority: 1\n");
		fclose($fp);
		chmod($file,0777);

		system("mv -f ".escapeshellarg($file)." /var/spool/asterisk/outgoing/");
	}

	/**
	* Given a country code and possible NPA/NXX, determine where in the world we're at.
	*/
	function where_is(&$db, $countrycode, $npa, $nxx)
	{
		if ($countrycode == 1) {
			# We are in the USA, use npa/nxx lookup
			if ($npa == "800" || $npa == "866" || $npa == "877" || $npa == "888")
				return "Toll-free";
			$rs =& $db->Execute("SELECT locName, locState FROM ".AGILE_DB_PREFIX."voip_npa_nxx WHERE country_code='1' and npa=".$db->qstr($npa)." and nxx=".$db->qstr($nxx));
			if ($rs && $rs->RecordCount())
			return $rs->fields[0].", ".$rs->fields[1];
			else
			return "Unknown";
		} else {
			/*
			mysql> describe ab_voip_iso_country_code_map;
			+------------------+-------------+------+-----+---------+-------+
			| Field            | Type        | Null | Key | Default | Extra |
			+------------------+-------------+------+-----+---------+-------+
			| id               | int(11)     |      | PRI | 0       |       |
			| country_code     | varchar(16) |      | MUL |         |       |
			| iso_country_code | char(3)     |      |     |         |       |
			| iso_sub_code     | char(3)     |      |     |         |       |
			+------------------+-------------+------+-----+---------+-------+
			4 rows in set (0.00 sec)
			
			mysql> describe ab_voip_iso_country_code;
			+-------+-------------+------+-----+---------+-------+
			| Field | Type        | Null | Key | Default | Extra |
			+-------+-------------+------+-----+---------+-------+
			| id    | int(11)     |      | PRI | 0       |       |
			| code  | char(3)     |      | UNI |         |       |
			| name  | varchar(64) |      |     |         |       |
			+-------+-------------+------+-----+---------+-------+
			3 rows in set (0.00 sec)
			*/
			$sql = "SELECT b.name FROM ".AGILE_DB_PREFIX."voip_iso_country_code_map a left join ".AGILE_DB_PREFIX."voip_iso_country_code b
			on (a.iso_country_code=b.code)
			WHERE a.country_code like ".$db->qstr($countrycode."%");
			$rs =& $db->Execute($sql);
			if ($rs && $rs->RecordCount())
			return $rs->fields[0];
			else return "Unknown";
		}
		return "Unknown";	
	}
	
	/** 
	* Given a DID, returns the e.164 representation and the country code with possible npa/nxx if USA. If successful,
	* a true is returned, otherwise false is returned.
	*
	* @param $number Input telephone number
	* @param $e164 Output cleaned number in E.164 format
	* @param $countrycode Output country code designator
	* @param $npa Output NPA code if USA country
	* @param $nxx Output NXX code if USA country
	*/
	function e164($number, &$e164, &$countrycode, &$npa, &$nxx)
	{
		if(function_exists('agileco_e164')) {
			if(($r = agileco_e164($number,$this->voip_default_prefix)) === false)
				return false;
			$e164 = $r['e164'];
			$countrycode = $r['country_code'];
			$npa = $r['npa'];
			$nxx = $r['nxx'];
			#echo "<pre>".print_r($r, true)."</pre>";
			return true;
		}	 
		$e164 = ""; $countrycode = ""; $npa = ""; $nxx = "";
		
		if (preg_match("/[a-zA-Z]/",$number)) return false;
		if (!strncmp($number, "+", 1)) {
			# if the number has a leading plus, strip it.
			$number = substr($number,1);
		}
		if (strlen($number) == 7) {
			# USA local dialing, need to prefix the country code and local npa
			$e164 = "+1".$this->voip_default_prefix.$number;
		}
		if (!strncmp($number, "0111", 4)) {
			# Screwed up USA call, strip the international prefixing
			$e164 = "+".substr($number, 3);
		}
		/* UK Specific hack */
		if (!strncmp($number, "44", 2)) {
			$e164 = "+011".$number;
		}
		if (!strncmp($number, "0", 1) && strlen($number) == 11) {
			$e164 = "+01144".$number;
		}
		/* End UK Specific hack */
		if (strlen($number) == 10 && strncmp($number,"011",3)) {
			# USA Call without the country code selection
			$e164 = "+1".$number;
		}
		if ($e164 == "") {
			$e164 = "+".$number;
		}

		# ok, cleaned the number. Now, what's the country code?
		if (!strncmp($e164, "+011", 4)) {
			# international
			$countrycode = $this->parse_country_code($e164);
		} else {
			# USA
			$countrycode = "1";
			$npa = substr($e164, 2, 3);
			$nxx = substr($e164, 5, 3);
		}
		if (strlen($countrycode) && strlen($number))
			return true;
		return false;
	}
	
	
	/**
	* Parses the E.164 number for a correct country code. NOTE: Doesn't handle the USA right. Returns the country code.
	* Example: +011xxxxxxx
	*/
	function parse_country_code($e164)
	{
		if(function_exists('agileco_parse_country_code')) {
			#echo 'calling agileco_parse_country_code!<br>';
			return agileco_parse_country_code($e164);
		}	
    $numdigs = 2;
    $d1 = substr($e164, 4, 1);
    $d2 = substr($e164, 5, 1);

		switch ($d1) {
			case '1':
			case '7':
				$numdigs = 1;
				break;
			case '2':
				if ($d2 != '0' && $d2 != '7') {
					$numdigs = 3;
				}
				break;
			case '3':
				if ($d2 == '5' || $d2 == '7' || $d2 == '8') {
					$numdigs = 3;
				}
				break;
			case '4':
				if ($d2 == '2') {
					$numdigs = 3;
				}
				break;
			case '5':
				if ($d2 == '0' || $d2 == '9') {
					$numdigs = 3;
				}
				break;
			case '6':
				if ($d2 >= 7) {
					$numdigs = 3;
				}
				break;
			case '8':
				if ($d2 == '0' || $d2 == '3' || $d2 == '5' || $d2 == '7') {
					$numdigs = 3;
				}
				break;
			case '9':
				if ($d2 == '6' || $d2 == '7' || $d2 == '9') {
					$numdigs = 3;
				}
				break;
			default:
				$numdigs = 0;
				break;
		}
	
		if ($d2<0 || $d2>9) {
			$numdigs = 0;
		} else {
			if (strlen($e164) < $numdigs) {
				$numdigs = 0;
			}
		}
		if ($numdigs) {
			return substr($e164, 4, $numdigs);
		}
		return "";		
	}
	
	/**
	* Get the activity for the week needed
	*/
	function activity($VAR) {
		global $smarty;

		$fdids = $this->get_fax_dids(SESS_ACCOUNT);
		$dt = new DateFunc;
		if (empty($VAR['wnum']))
			$wnum = $dt->week_of_year(date('m'),date('d'),date('Y'));
		else
			$wnum = $VAR['wnum'];

		$smarty->assign('wnum',$wnum);
		$weeks = $dt->getWeekArray();
		$wtext[] = "-- Please Select --";
		foreach ($weeks as $w) {
			$wtext[$w['week']] = $w['text'];
			if ($w['week'] == $wnum) {
				$b = $w['begin'];
				$e = $w['end']; $e+=86400;
			}
		}
		
		$smarty->assign('weeks', $wtext);

		$db=&DB();
		$p=AGILE_DB_PREFIX;
		
		// get dids
		$sql_in='';
		$sql_out='';
		$dids = $this->get_all_dids(SESS_ACCOUNT);
		$dids = $this->normalize_dids($dids);
 
		if(count($dids)>0) {
			foreach($dids as $did) {
				if($sql_in!='') {
					$sql_in .= ' OR ';
					$sql_out .=  ' OR ';
				}
				$sql_in .= " dst = $did ";
				$sql_out .= " src = $did ";
				if(strncmp($did,"1",1) && strncmp($did,"0",1)) {
					$sql_in .= " OR dst = ".$db->qstr("1".$did)." ";
					$sql_out .= " OR src = ".$db->qstr("1".$did)." ";
				}
			}
		} else {
			$rs =& $db->Execute(sqlSelect($db,"account","username","id=".SESS_ACCOUNT));
			$sql_out = "accountcode=::".$rs->fields[0]."::";
		}		 			
		# gather prepaid account pins
		$pins = array();
		$rs =& $db->Execute(sqlSelect($db,"voip_prepaid","pin","account_id=".SESS_ACCOUNT));
		while ($rs && !$rs->EOF) {
			$pins[] = $rs->fields[0];
			$rs->MoveNext();
		}
		foreach ($pins as $pin) {
			if ($sql_out!='') {
				$sql_out .= ' OR ';
			}
			if ($sql_in!='') {
				$sql_in .= ' OR ';
			}
			$sql_out .= " accountcode=::cc:".$pin.":: ";
			$sql_in .= " dst = ::".$pin.":: ";
		}

		# Get currency
		$rs =& $db->Execute($sql=sqlSelect($db,"currency","symbol","id=".DEFAULT_CURRENCY));
		$smarty->assign('currency', $rs->fields[0]);
		
		# Get last 25 incoming:
		$rs =& $db->Execute(sqlSelect($db,"voip_cdr","date_orig, clid, src, dst, ceiling(duration/60) as duration, amount, lastapp"," ( $sql_in ) AND (lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') AND disposition='ANSWERED' AND account_id = ".SESS_ACCOUNT." AND date_orig>=$b AND date_orig<=$e","date_orig DESC"));
		if($rs && $rs->RecordCount() > 0) {
			$i = 0;
			while(!$rs->EOF) {
				$in[$i]=$rs->fields;
				if (strcasecmp($rs->fields['lastapp'], 'VoiceMail') == 0)
					$in[$i]['type'] = 'v';
				else if (in_array($rs->fields['dst'], $fdids))
					$in[$i]['type'] = 'f';
				$cc = ""; $npa = ""; $nxx = ""; $e164 = "";
				if ($this->e164($rs->fields['src'], $e164, $cc, $npa, $nxx)) {
					$in[$i]['location'] = $this->where_is($db, $cc, $npa, $nxx);
				}										
				$i++;
				$rs->MoveNext();
			}
		} 
				
		# Get last 25 outgoing:
		$rs =& $db->Execute($sql = sqlSelect($db,"voip_cdr","date_orig, clid, dst, ceiling(duration/60) as duration, amount, lastapp"," ( $sql_out ) AND (lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') AND disposition='ANSWERED' AND account_id = ".SESS_ACCOUNT." AND date_orig>=$b AND date_orig<=$e","date_orig DESC"));
		if($rs && $rs->RecordCount() > 0) {
			$i = 0;
			while(!$rs->EOF) {
				$out[$i]=$rs->fields;
				if (strcasecmp($rs->fields['lastapp'], 'VoiceMail') == 0)
					$out[$i]['type'] = 'v';
				foreach ($pins as $pin) {
					if (strcmp($rs->fields['accountcode'], "cc:".$pin)==0) {
						$out[$i]['type'] = 'c';
					}
				}
				$cc = ""; $npa = ""; $nxx = ""; $e164 = "";					
				if ($this->e164($rs->fields['dst'], $e164, $cc, $npa, $nxx)) {
					$out[$i]['location'] = $this->where_is($db, $cc, $npa, $nxx);
				}										
				$i++;				
				$rs->MoveNext();
			}
		}	

		$smarty->assign('in', $in);
		$smarty->assign('out', $out); 		
	}

	function normalize_dids($dids)
	{
		foreach($dids as $did) {
			$out[] = $did;
			if(!strncmp($did,"011",3))
				$out[] = substr($did,3);
			if(!strncmp($did,"1",1))
				$out[] = substr($did,1);
		}
		return $out;
	}
	
	/**
	* Get the last 25 placed and received calls for the user
	* @todo Get some call statistics daily/weekly/monthly for the user
	*/
	function overview($VAR) {
		global $smarty;

		// validate logged in
		if(!SESS_LOGGED) return false;
		
 		$fdids = $this->get_fax_dids(SESS_ACCOUNT);
		// get dids
		$sql_in='';
		$sql_out='';
		$dids = $this->get_all_dids(SESS_ACCOUNT);
		$dids = $this->normalize_dids($dids);
		 
		$db=&DB();
		$p=AGILE_DB_PREFIX;		 
		if(count($dids)>0) {
			foreach($dids as $did) {
				if($sql_in!='') {
					$sql_in .= ' OR ';
					$sql_out .=  ' OR ';
				}
				$sql_in .= " dst = ".$db->qstr($did)." ";
				$sql_out .= " src = ".$db->qstr($did)." ";
				if(strncmp($did,"1",1) && strncmp($did,"0",1)) {
					$sql_in .= " OR dst = ".$db->qstr("1".$did)." ";
					$sql_out .= " OR src = ".$db->qstr("1".$did)." ";
				}
			}
		} else {
			$rs =& $db->Execute(sqlSelect($db,"account","username","id=".SESS_ACCOUNT));
			$sql_out = "accountcode=::".$rs->fields[0]."::";
		}		 			
		# gather prepaid account pins
		$pins = array();
		$rs =& $db->Execute(sqlSelect($db,"voip_prepaid","pin","account_id=".SESS_ACCOUNT));
		while ($rs && !$rs->EOF) {
			$pins[] = $rs->fields[0];
			$rs->MoveNext();
		}
		foreach ($pins as $pin) {
			if ($sql_out!='') {
				$sql_out .= ' OR ';
			}
			if ($sql_in!='') {
				$sql_in .= ' OR ';
			}
			$sql_out .= " accountcode=::cc:".$pin.":: ";
			$sql_in .= " dst = ::".$pin.":: ";
		}
				
		# Get last 25 incoming:
		$rs =& $db->Execute(sqlSelect($db,"voip_cdr","id, date_orig, clid, src, dst, ceiling(duration/60) as duration, lastapp"," ( $sql_in ) AND (lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') AND disposition='ANSWERED' AND account_id = ".SESS_ACCOUNT,"date_orig DESC LIMIT 25",25));
		if($rs && $rs->RecordCount() > 0) {
			$i = 0;
			while(!$rs->EOF) {
				$in[$i]=$rs->fields;
				if (strcasecmp($rs->fields['lastapp'], 'VoiceMail') == 0)
					$in[$i]['type'] = 'v';
				else if (in_array($rs->fields['dst'], $fdids))
					$in[$i]['type'] = 'f';
				$cc = ""; $npa = ""; $nxx = ""; $e164 = "";
				if ($this->e164($rs->fields['src'], $e164, $cc, $npa, $nxx)) {
					$in[$i]['location'] = $this->where_is($db, $cc, $npa, $nxx);
				}					
				$i++;				
				$rs->MoveNext();
			}
		} 
		#echo "\n\n<!-- $sql -->\n\n";
		
		# Get last 25 outgoing:
		$rs =& $db->Execute(sqlSelect($db,"voip_cdr","id, accountcode, date_orig, clid, dst, ceiling(duration/60) as duration, lastapp"," ( $sql_out ) AND (lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') AND disposition='ANSWERED' AND account_id = ".SESS_ACCOUNT,"date_orig DESC LIMIT 25",25));
		if($rs && $rs->RecordCount() > 0) {
			$i = 0;
			while(!$rs->EOF) {
				$out[$i]=$rs->fields;
				if (strcasecmp($rs->fields['lastapp'], 'VoiceMail') == 0)
					$out[$i]['type'] = 'v';
				foreach ($pins as $pin) {
					if (strcmp($rs->fields['accountcode'], "cc:".$pin)==0) {
						$out[$i]['type'] = 'c';
					}
				}
				$cc = ""; $npa = ""; $nxx = ""; $e164 = "";					
				if ($this->e164($rs->fields['dst'], $e164, $cc, $npa, $nxx)) {
					$out[$i]['location'] = $this->where_is($db, $cc, $npa, $nxx);
				}										
				$i++;				
				$rs->MoveNext();
			}
		}	
 

		echo $sql; 
		
		$smarty->assign('in', $in);
		$smarty->assign('out', $out); 
	}
	
	 
	/**
	* Get user features for selected did
	*/
	function features($VAR) {
		
		// validate logged in
		if(!SESS_LOGGED) return false;

		global $smarty;
		 
		// get the selected did
		if(!empty($VAR['voip_did_id']) && is_numeric($VAR['voip_did_id'])) 
		{ 
			$smart=$this->get_auth_did($VAR['voip_did_id']);
			$smarty->assign('record',$smart);  
		} 
		else 
		{
			$dids = $this->get_all_dids(SESS_ACCOUNT);
			if(is_array($dids)) $dids = $dids[0];
			if(!$dids) return false;
			$smart=$this->get_auth_did($dids);
			$smarty->assign('record',$smart); 
		}	  
	}
	
	/**
	* verify that a specific did is validated for the current account
	*/
	function get_auth_did($did) {
		$db=&DB(); 
		if($did) $sql = "id = ::$did:: AND "; else $did =''; 
		$rs = & $db->Execute($sql=sqlSelect($db,"voip_did","*","$sql account_id = ".SESS_ACCOUNT));	 
		
		if($rs && $rs->RecordCount()>0) { 
			// get the voicemail email setting
			if(!empty($rs->fields['voicemailenabled'])) { 
		  		$vm = & $db->Execute($sql = sqlSelect($db,"voip_vm","email","mailbox = ::{$rs->fields['did']}:: AND account_id = ".SESS_ACCOUNT));	
		  		$rs->fields['vm_email'] = $vm->fields['email'];  
		  	}	 
		  	// is callwaiting enabled or disabled
		  	$callwaiting = 1;
		  	$cw =& $db->Execute($sql=sqlSelect($db,"voip_sip","data","keyword=::incominglimit:: and sip=::".$rs->fields['did']."::"));
		  	if(!empty($cw->fields['data'])) {
		  		if($cw->fields['data'] == 1) {
		  			$callwaiting = 0;
		  		}
		  	}
		  	$rs->fields['sip_callwaiting'] = $callwaiting;	 
		  	return $rs->fields;		 
		} else {
			return false;
		}
	}
	
	/**
	* Update user features for selected did
	*/	
	function update_features($VAR) {
		if(!SESS_LOGGED) return false;

		// get the selected did
		if(!empty($VAR['voip_did_id']) && is_numeric($VAR['voip_did_id'])) 
		{
			if($flds = $this->get_auth_did($VAR['voip_did_id']))
			{
				$fields['voicemailafter'] 			= @$VAR['voip_voicemailafter']; 
				$fields['callforwardingenabled'] 	= @$VAR['voip_callforwardingenabled'];
				$fields['cfringfor'] 				= @$VAR['voip_cfringfor'];
				$fields['cfnumber'] 				= @$VAR['voip_cfnumber'];
				$fields['busycallforwardingenabled']= @$VAR['voip_busycallforwardingenabled'];
				$fields['bcfnumber'] 				= @$VAR['voip_bcfnumber'];
				$fields['faxemail'] 				= @$VAR['voip_faxemail'];
				$fields['failovernumber'] 			= @$VAR['voip_failovernumber'];
				$fields['remotecallforwardnumber'] 	= @$VAR['voip_remotecallforwardnumber']; 
				$db=&DB();
		  		$rs = & $db->Execute($sql=sqlUpdate($db,"voip_did",$fields,"id = ::{$flds['id']}::"));
		  	  
		  		if(!empty($VAR['voip_vm_email']) && !empty($flds['voicemailenabled'])) {
		  			$fields2['email'] = $VAR['voip_vm_email'];
		  			$rs = & $db->Execute($sql = sqlUpdate($db,"voip_vm",$fields2,"mailbox = ::{$flds['did']}:: AND account_id = ".SESS_ACCOUNT)); 
		  		} 
		  		
		  		# update the call waiting setting
		  			if(!empty($VAR['sip_callwaiting'])) {
		  				# delete the incominglimit
		  				$sql = "DELETE FROM ".AGILE_DB_PREFIX."voip_sip WHERE sip=".$flds['did']." and keyword='incominglimit' and site_id=".DEFAULT_SITE;
		  				$db->Execute($sql);
		  			} else {
		  				$f['data'] = "1";
    					$f['keyword'] = "incominglimit";
    					$f['sip'] = $flds['did'];
    					$sql = sqlInsert($db, "voip_sip", $f);
    					$db->Execute($sql);
		  			}
			}
		}		
	}

	// get available parent service ids
	function menu_parent_service($VAR) {
 
		if(!empty($VAR['account_id'])) 
			$account_id = $VAR['account_id'];
		else
			$account_id = SESS_ACCOUNT;
			
		$db=&DB();
		$rs=&$db->Execute($sql=sqlSelect($db,array("voip_did","service"),"A.id,A.service_id,A.did","A.service_id = B.id AND (A.rxfax=0 or A.rxfax is null) AND (A.conf=0 or A.conf is null) AND (A.remotecallforward=0 or A.remotecallforward is null) AND B.active=1 AND B.account_id=".$account_id,false,false,"DISTINCT"));
		if($rs && $rs->RecordCount() > 0) {
			
			$return = '<select name="attr[parent_service_id]">';
			$return .= '<option value="" selected></option>'; 
			if($rs && $rs->RecordCount() > 0) 
			{
				while(!$rs->EOF) 
				{ 
					$return .= '<option value="' . $rs->fields['service_id'] . '">' . $rs->fields["did"] . '</option>';	
					$rs->MoveNext();			
				}
			}   
			$return .= '</select>';	
			echo $return;		
		} else {
			echo "No associated accounts found";
		}
	}

	
	// function 
	function get_did_plugin_countries($id, &$plugins) {
		$countries = false;
		$db=&DB();
		$rs = & $db->Execute($sql=sqlSelect($db,"product","prod_plugin_data","id = ::$id::"));
		 
		if($rs && $rs->RecordCount() > 0) { 
			@$plugin = unserialize($rs->fields['prod_plugin_data']);
			@$plugins = $plugin['voip_did_plugins'];
	  
			if(is_array($plugins) && count($plugins > 0)) {
				$sql = '';
				foreach($plugins as $key=>$plgid) {
					if($sql!='') $sql .= " OR ";
					$sql .= " id = $plgid ";
				}
				$rs = & $db->Execute(sqlSelect($db,"voip_did_plugin","avail_countries","( $sql )"));
				if($rs && $rs->RecordCount() > 0) {
					while(!$rs->EOF) {
						$carr = unserialize($rs->fields['avail_countries']);
						foreach($carr as $cid) $countries["$cid"] = $cid;
						$rs->MoveNext();
					}
				}
			} else {
				$plugins = array();
			}
		}
		return $countries;
	}
	
	// get available countries
	function menu_countries($VAR) {
		header('Pragma: no-cache');
		header('Cache-Control: no-cache, must-revalidate');

		if(!$did_plugins = $this->get_did_plugin_countries($VAR['id'],$plugins)) { 
			echo '-- No Countries --';
			return;
		} 

		$countries = new didCountries($plugins);
		$js = '<select id="voip_country" name="attr[country]" onChange="voipChangeCountry(this.value)">';
		$js .= '<option value="" selected>--- Pick a Country ---</option>';	 	
		while($c = $countries->getCountry()) {
			$js .= '<option value="' . $c->getCode() . '">' . $c->getName() . '</option>';
		}
		$js .= '</select>';
		echo $js;
	}
	
	 
	// 
	function menu_states($VAR) 
	{
		header('Pragma: no-cache');
		header('Cache-Control: no-cache, must-revalidate');		
		$did_plugins = $this->get_did_plugin_countries($VAR['id'],$plugins);

		$areas = new didAreas(1,$plugins);
		#echo "<pre>".print_r($areas,true)."</pre>";
		$areas_seen = array();
		$js = '<select id="voip_state" name="attr[state]" onChange="voipChangeState(this.value)">';
		$js .= '<option value="" selected>--- Pick a State ---</option>';
		while($area = $areas->getArea()) {		 
			#echo print_r($area,true);			
			if(!isset($areas_seen[$area->getState()])) {			
				$js .= '<option value="' . $area->getState() . '">' . $area->getState() . '</option>';
				$areas_seen[$area->getState()] = true;
			}
		}
		$js .= '</select>';
		echo $js;
	}
  
	// return location menu
	function menu_location($VAR) 
	{	
		header('Pragma: no-cache');
		header('Cache-Control: no-cache, must-revalidate'); 
		$did_plugins = $this->get_did_plugin_countries($VAR['id'],$plugins);
		
		$cc = 1; 
		if(!empty($VAR['country'])) 
			$cc = $VAR['country'];
		#echo "alert('$cc');"; exit;
		$areas = new didAreas($cc,$plugins);
		$js = 'menuClearOptions("voip_location");';
		$js .= "menuAppendOption('voip_location', '', '-- Select A Location --');";
		$count = 0; 
		while($area = $areas->getArea()) {		
			if($cc!=1) {		
				$js .= "menuAppendOption('voip_location', '{$cc}:".$area->getAreacode()."', '".$area->getName()." (".$area->getAreacode().")');";
			} else if($area->data['locState']==$VAR['state']) {
				$js .= "menuAppendOption('voip_location', '".$area->getNpa()."-".$area->getNxx()."', '".$area->getName()." (".$area->getNpa()."-".$area->getNxx().")');";
			}
			$count++;
		}
		if($count)
			echo $js;
		else
			echo 'document.write("Sorry, none available at this time. Please check again later.");';
		return;
	}
	
	
	// return location menu
	function menu_station($VAR) 
	{  
		header('Pragma: no-cache');
		header('Cache-Control: no-cache, must-revalidate');
		$did_plugins = $this->get_did_plugin_countries($VAR['id'],$plugins);
		#echo "alert(\"".str_replace("\n","\\\n",str_replace("\"","\\\"",print_r($VAR,true)))."\");";return;
				
		#if(empty($VAR['location']) && empty($VAR['country'])) return false; 
		$l = $VAR['location'];
		if (strchr($l,':')) {
			$cn = explode(':', $l);
			$data['country_code'] = $cn[0];
			$data['areacode'] = $cn[1];			
		} else {
			$cn = explode('-', $l);
			$data['country_code'] = 1;
			$data['npa'] = $cn[0];
			$data['nxx'] = $cn[1];
		}
		$area = new didArea($data['country_code'], $data);
		#echo "alert(\"".str_replace("\n","\\\n",str_replace("\"","\\\"",print_r($area,true)))."\");"; return;
		
		$js = 'menuClearOptions("voip_station"); ';
		$js .= "menuAppendOption('voip_station', '', '-- Select A Station --'); "; 
		$dids = $area->getStations($plugins);
		#echo "alert(\"".str_replace("\n","\\\n",str_replace("\"","\\\"",print_r($dids,true)))."\");"; return;
	 	foreach($dids as $did) { 
	 		#if($data['country_code'] == 1) {
	 			$js .= "menuAppendOption('voip_station', '".$did[0]."', '{$did[1]}'); ";
	 		#} else {
	 		#	;
	 		#}
	 	}
	 	echo $js;
	 	return;
	 	
		$db=&DB();
		$p = AGILE_DB_PREFIX; 				
		if(!empty($VAR['location'])) 
		{
			
			$l = $VAR['location']; 
			if(eregi(':', $l)) 			// passed country_code:region
			{									
				$cn = explode(':', $l);
				$ccode = $cn[0];
				$npa   = $cn[1];
				
				$sql = "select distinct left(A.station,4) as npa,B.locName, station from {$p}voip_pool AS A 
				inner join {$p}voip_npa_nxx AS B on (left(A.station,4)=B.npa AND 
				A.country_code='{$ccode}' AND B.country_code='{$ccode}' AND B.npa='{$npa}') 
				WHERE (A.account_id IS NULL OR A.account_id = 0) AND 
				(A.date_reserved IS NULL OR A.date_reserved = 0) AND 
				A.voip_did_plugin_id in (".join(",",$plugins).") AND
				A.site_id=".DEFAULT_SITE." ORDER BY B.locName LIMIT 0,50";  
				// loop through results
				$rs = $db->Execute($sql);
				if($rs && $rs->RecordCount() > 0)  {	 
					while(!$rs->EOF) { 
						if(!empty($rs->fields["station"]))
						$js .= "menuAppendOption('voip_station', '011". $ccode . $rs->fields["station"]."', '{$ccode} {$rs->fields["station"]}'); ";
						$rs->MoveNext();
					} 
				}  else {
					$js = 'document.write("Sorry, none available at this time. Please check again later.");';
				} 						 				
			} else {
				$np=explode('-',$l);		// passed npa-nxx
				$npa=$np[0];
				$nxx=$np[1];			 
				$sql = "select distinct A.country_code,A.npa,A.nxx,A.station
						FROM {$p}voip_pool AS A
						left join {$p}voip_npa_nxx AS B
						on (A.npa=B.npa and A.nxx=B.nxx AND B.country_code='1') 
						WHERE (A.account_id IS NULL OR A.account_id = 0)
						AND (A.date_reserved IS NULL OR A.date_reserved = 0)	
						AND A.npa = " . $db->qstr($npa) . "
						AND A.nxx = " . $db->qstr($nxx) . "
						AND A.voip_did_plugin_id in (".join(",",$plugins).")
						AND A.site_id=".DEFAULT_SITE."  
						LIMIT 0,50";  
	 
				// loop through results
				$rs = $db->Execute($sql);
				if($rs && $rs->RecordCount() > 0)  {	 
					while(!$rs->EOF) { 
						if(!empty($rs->fields["station"]))
						$js .= "menuAppendOption('voip_station', '". $rs->fields["country_code"].$rs->fields["npa"].$rs->fields["nxx"].$rs->fields['station'] ."', '{$rs->fields["npa"]}-{$rs->fields["nxx"]}-{$rs->fields["station"]}'); ";
						$rs->MoveNext();
					} 
				}  else {
					$js = 'document.write("Sorry, none available at this time. Please check again later.");';
				}  	
			}
		} 
		else 
		{
			$country = $VAR['country'];
			$sql = "SELECT DISTINCT B.country_code, B.station from {$p}voip_pool AS B 
					LEFT JOIN {$p}voip_iso_country_code_map AS A  ON (B.country_code=A.country_code) 
					WHERE ( account_id IS NULL or account_id = 0) 
					AND ( B.date_reserved IS NULL or B.date_reserved = 0 )
					AND A.iso_country_code=".$db->qstr($country)."
					AND B.voip_did_plugin_id in (".join(",",$plugins).")
					and A.site_id=".DEFAULT_SITE." AND B.site_id=".DEFAULT_SITE." 
					LIMIT 0,50";	
			 
			$rs = $db->Execute($sql);
			if($rs && $rs->RecordCount() > 0) {	 
				while(!$rs->EOF) { 
					if(!empty($rs->fields["station"]))
					$js .= " menuAppendOption('voip_station', '011". $rs->fields["country_code"] . $rs->fields["station"] ."', '{$rs->fields["country_code"]}{$rs->fields["station"]}'); ";
					$rs->MoveNext();
				} 
			} else {
				$js = 'document.write("Sorry, none available at this time. Please check again later.");';
			}				
		}
		  
		echo $js; 	
		ob_end_flush();
		return true; 
	}

	/** Returns the fields from voip_pool for a given DID entry.
	*/
	function get_did_e164($did)
	{
		$db =& DB();

 		$cc = ""; $npa = ""; $nxx = ""; $e164 = "";
		if ($this->e164($did, $e164, $cc, $npa, $nxx)) {
			if ($cc == '1') {
				$station = substr($e164, 8);
				$where = "country_code=1 and npa=::$npa:: and nxx=::$nxx:: and station=::$station::";
			} else {
				$station = substr($e164, 4 + strlen($cc));
				$where = "country_code=::$cc:: and station=::$station::";
			}
			$rs = $db->Execute(sqlSelect($db, "voip_pool", "*", $where));
			if (!$rs)	return false;
			return $rs->fields;
		}
		return false;	
	}
	
	/** Save the configuration.
	 */
	function config($VAR)
	{
		global $C_debug;
		$db = & DB();
		 
		# define the validation class
		include_once(PATH_CORE . 'validate.inc.php');
		$validate = new CORE_validate;		
		$arr['min_len'] = 4;
		$arr['max_len'] = 4;
		
		if(is_numeric($VAR['voip_vm_passwd']) && !empty($VAR['voip_intrastate']))
		{
			$fields['voip_vm_passwd'] = $VAR['voip_vm_passwd'];
			$fields['voip_intrastate'] = $VAR['voip_intrastate'];
			$fields['voip_secret_gen'] = $VAR['voip_secret_gen'];
			$fields['voip_default_prefix'] = $VAR['voip_default_prefix'];
			$fields['prepaid_low_balance'] = $VAR['prepaid_low_balance'];
			$fields['auth_domain'] = $VAR['auth_domain'];
			$fields['perform_normalization'] = $VAR['perform_normalization'];
			$fields['normalization_min_len'] = $VAR['normalization_min_len'];
			$rs = $db->Execute( sqlSelect($db, "voip", "id", "site_id=::".DEFAULT_SITE."::") );
			if ($rs && !$rs->EOF) {
				$db->Execute( sqlUpdate($db, "voip", $fields, "site_id=::".DEFAULT_SITE."::") );
			} else {
				$db->Execute( sqlInsert($db, "voip", $fields) );
			}			
			$C_debug->alert("Saved!");
		} else {
			$C_debug->alert("Problems while saving:".$db->ErrorMsg());
		}  
	}

	/** Load the configuration variables into smarty
	 */
	function config_get($VAR)
	{
		global $smarty;
		$db = & DB(); 
		$sql = sqlSelect($db, "voip", "*", ""); 
		$rs = $db->Execute($sql);
		$smarty->assign('config',$rs->fields);
	}

	/** Return all available DIDs the customer owns.
	 * @param $account_id: The account to return dids for
	 */
	function get_all_dids($account_id)
	{
		return $this->get_all_dids_internal($account_id, 0);
	}
	
	/** Return all DIDs with voice mail active.
	 * @param $account_id: The account to return dids for
	 */
	function get_voicemail_dids($account_id)
	{
		return $this->get_all_dids_internal($account_id, 1);
	}
	
	/** Return all DIDs with fax active.
	 * @param $account_id: The account to return dids for
	 */
	function get_fax_dids($account_id)
	{
		return $this->get_all_dids_internal($account_id, 2);
	}
	
	/** Internal function used to gather an accounts available DIDs.
	 *  @param $VAR The AB passed array
	 *  @param $filter A flag to specify the type of DIDs to return. 0=ALL, 1=VM, 2=FAX, 3=CONFERENCE
	 */
	function get_all_dids_internal($account_id, $filter)
	{
		$db = & DB();
		$rs = & $db->Execute($sql=sqlSelect($db,"voip_did","did,voicemailenabled,rxfax,conf","active=1 AND account_id = ::$account_id::")); 
		#echo $sql;
		$dids = array(); 
		if ($rs && $rs->RecordCount() > 0) {
			while (!$rs->EOF) { 
				$did = $rs->fields['did']; 
				switch ($filter) {
				case 0:										//ALL
					array_push($dids, $did);
					break;
				case 1:
					if ($rs->fields['voicemailenabled']) {	//VM
						array_push($dids, $did);
					}
					break;
				case 2:
					if ($rs->fields['rxfax']) {				//FAX
						array_push($dids, $did);
					}
					break;
				case 3:
					if ($rs->fields['conf']) {				//CONF
						array_push($dids, $did);
					}
					break;
				default:
		        	global $C_debug;
		        	$C_debug->error('voip.inc.php','get_all_dids_internal','Invalid filter passed: '.$filter);
				}	
				$rs->MoveNext();
			}
		}
		return $dids;
	}

	function normalize(&$db)
	{
		$count = 0;
		$sql = sqlSelect($db, "voip_cdr", "src, dst, id", "(rated is null or rated=0)");
		#echo $sql."<BR>";
		$rs = $db->Execute($sql);
		while (!$rs->EOF) {
			$src = $rs->fields['src'];
			$dst = $rs->fields['dst'];

			$e164 = ""; $cc = ""; $npa = ""; $nxx = "";
			if (strlen($src)>=$this->normalization_min_len && $this->e164($src, $e164, $cc, $npa, $nxx)) {
				$src = substr($e164,1);
			}
			$e164 = ""; $cc = ""; $npa = ""; $nxx = "";
			if (strlen($dst)>=$this->normalization_min_len && $this->e164($dst, $e164, $cc, $npa, $nxx)) {
				$dst = substr($e164,1);
			}
			#echo "src=".$rs->fields['src']." dst=".$rs->fields['dst']."<br>";
			#echo "esrc=".$src." edst=".$dst."<br><br>";
			#$f = array('src' => $src, 'dst' => $dst, 'rated' => '2');
			#$sql = sqlUpdate($db,"voip_cdr",$f,"id=::".$rs->fields['id']);
			$sql = "UPDATE ".AGILE_DB_PREFIX."voip_cdr SET 
				src=".$db->qstr($src).", dst=".$db->qstr($dst).", rated=2
				WHERE id=".$db->qstr($rs->fields['id']);
			#echo $sql."<br>";
			$db->Execute($sql); 
			$count++;
			$rs->MoveNext();
		}
		echo "Normalized $count records...\n";
	}
		
	// Call as task - voip:task
	function task($VAR)
	{
		if(function_exists('agileco_parse_country_code')) {
			$this->c_task($VAR);
			return;
		}
		
		global $rate;
		$rate = array();
		$db = &DB(); 
		$rs = & $db->Execute( sqlSelect($db, "product", "id,prod_plugin_data", "prod_plugin_file=::VOIP:: and prod_plugin=1")); 
		while (!$rs->EOF) {
			$pdata = unserialize($rs->fields['prod_plugin_data']);
			$id = $rs->fields['id'];
			if ($pdata['rate_cdr'] == 1) {
				$products[] = $id;
			}
			$rs->MoveNext();
		}
		
		// no products to rate
		if(empty($products)) return false;

		# Load configuration
		$sql = sqlSelect($db, "voip", "voip_intrastate, voip_default_prefix, perform_normalization, normalization_min_len", "");
		$rs = $db->Execute($sql);
		$this->voip_intrastate = explode(",",ereg_replace("[[:space:]]","",$rs->fields['voip_intrastate']));
		$this->voip_default_prefix = $rs->fields['voip_default_prefix'];
		$this->normalization_min_len = $rs->fields['normalization_min_len'];
		$this->perform_normalization = $rs->fields['perform_normalization'];
		
		ob_start();
		
		# normalize the CDR records
		echo "Begin normalization...\n";
		if($this->perform_normalization) {
			$this->normalize($db);
		}
		echo "Finished normalization...\n";
		
		# rate prepaid cards, non-SIP prepaid
		$rs =& $db->Execute(sqlSelect($db,"voip_prepaid","pin, account_id, product_id, voip_did_id","(voip_did_id=0 or voip_did_id is null)"));
		if ($rs && $rs->RecordCount() > 0) {
			while (!$rs->EOF) {
				$dp = 0;
				unset($dids);
				$dids[$dp]['start'] = 0;
				$dids[$dp]['end'] = mktime(0,0,0,date('m')+1,1,date('Y'));
				$dids[$dp]['accountcode'] = "cc:".$rs->fields['pin'];
				echo "Rating calling card PIN: ".$rs->fields['pin']."\n";
				# Load rating table configuration
				$rate = $this->load_rating_table($db, $rs->fields['product_id']);				
				$this->rate_calls($db, $db, $dids, $rs->fields, false);

				# Mark inbound calls
				if ($rs->fields['voip_did_id'] > 0) {
					$sql = "update ".AGILE_DB_PREFIX."voip_cdr SET amount=0, rated=1, account_id=".$db->qstr($rs->fields['account_id'])." where dst=".$db->qstr($rs->fields['pin'])." and rated=0 and site_id=".DEFAULT_SITE;
					echo $sql."\n";
					$db->Execute($sql);
				}
				$rs->MoveNext();
			}
		}

		echo "Begin SIP Prepaid rating...\n";
		$sql = "select account_id, username, prod_attr_cart, prod_plugin_data, date_last_invoice, date_next_invoice, b.product_id, b.id as service_id from ".AGILE_DB_PREFIX."account as a left join ".AGILE_DB_PREFIX."service as b on (a.id=b.account_id) where a.status=1 and prod_plugin_name='PREPAID' and b.active=1 and a.site_id=".DEFAULT_SITE." and b.site_id=".DEFAULT_SITE;
		echo $sql."\n";
		$rs =& $db->Execute($sql);
		if ($rs && $rs->RecordCount() > 0) {
			while (!$rs->EOF) {
						$dp = 0;
						unset($dids);
						$cart = @unserialize($rs->fields['prod_attr_cart']);
						$plugin = unserialize($rs->fields['prod_plugin_data']);
						if (isset($cart['station']) && isset($plugin['type']) && $plugin['type'] == 'did') {
							$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
							$dids[$dp]['end'] = $rs->fields['date_next_invoice'] + 86399;
							$dids[$dp]['did'] = $cart['station'];
							# Load rating table configuration
							$rate = $this->load_rating_table($db, $rs->fields['product_id']);
							if (is_array($rate)) {
								$this->rate_calls($db, $db, $dids, $rs->fields);
							}
						}
				$rs->MoveNext();
			}
		}

		echo "Begin postpaid rating...\n";
		# rate calls
		$sql = "select account_id, username, prod_attr_cart, prod_plugin_data, date_last_invoice, date_next_invoice, b.product_id, b.id as service_id, b.sku from ".AGILE_DB_PREFIX."account as a left join ".AGILE_DB_PREFIX."service as b on (a.id=b.account_id) where a.status=1 and prod_plugin_name='VOIP' and b.active=1 and product_id IN (".join(",",$products).") and a.site_id=".DEFAULT_SITE." and b.site_id=".DEFAULT_SITE;
		echo $sql."\n";
		$rs = $db->Execute($sql);
		$dp = 0;
		while (!$rs->EOF) {
			$dp = 0; unset($dids);
			$cart = @unserialize($rs->fields['prod_attr_cart']);
			$plugin = unserialize($rs->fields['prod_plugin_data']);

			$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
			$dids[$dp]['end']   = $rs->fields['date_next_invoice'];
			$dids[$dp]['did'] = @$cart['station'];
			if (strlen(@$cart['ported']))
				$dids[0]['did'] = $cart['ported'];
			$cc = ""; $e164 = ""; $npa = ""; $nxx = "";
			if ((!strlen($dids[0]['did']) && $plugin['rate_accountcode'] == 0)) {
				echo "Skipping service_id = ".$rs->fields['service_id']." (sku: ".$rs->fields['sku'].")\n";
			} else {
				if ($this->e164($dids[0]['did'],$e164,$cc,$npa,$nxx)) {
					$dids[0]['did'] = substr($e164,1);
					if ($cc == '1') {
						$dp++;
						$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
						$dids[$dp]['end']   = $rs->fields['date_next_invoice'] + 86399;
						$dids[$dp]['did'] = substr($e164,2);
						$dp++;
						$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
						$dids[$dp]['end']   = $rs->fields['date_next_invoice'] + 86399;
						$dids[$dp]['did'] = substr($e164,1);
					} else {
						$dp++;
						$dids[$dp]['start'] = $rs->fields['date_last_invoice'];
						$dids[$dp]['end']   = $rs->fields['date_next_invoice'] + 86399;
						$dids[$dp]['did'] = substr($e164,4);
					}
					
				}
				
				if (@$cart['parent_service_id'] > 0) {
					# echo "This is a virtual number, skipping record...";
					;
				} else {		
					# load virtual numbers on this parent service
					$sql = "select * from ".AGILE_DB_PREFIX."service where account_id=".$db->qstr($rs->fields['account_id'])." and active=1 and prod_plugin_name='VOIP' and site_id=".DEFAULT_SITE;
					echo $sql."\n";
					$rs1 = $db->Execute($sql); $i = 1;
					if ($rs1) {
						while (!$rs1->EOF) {
							$carttmp = @unserialize($rs1->fields['prod_attr_cart']);
							if (@$carttmp['parent_service_id'] == $rs->fields['service_id']) {
								# is this an actual virtual line?
								$ppd = unserialize($rs1->fields['prod_plugin_data']);
								if ($ppd['parent_enabled'] && $ppd['virtual_number']) {
									$dp++;
									$dids[$dp]['start'] = $rs1->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
									$dids[$dp]['end']   = $rs1->fields['date_next_invoice'] + 86399;
									$dids[$dp]['did'] = @$carttmp['station'];
									if (strlen($carttmp['ported']))
										$dids[$dp]['did'] = $carttmp['ported'];
									$cc = ""; $e164 = ""; $npa = ""; $nxx = "";
									if ($this->e164($dids[$dp]['did'],$e164,$cc,$npa,$nxx)) {
										$dids[$dp]['did'] = substr($e164,1);
										if ($cc == '1') {
											$dp++;
											$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
											$dids[$dp]['end']   = $rs->fields['date_next_invoice'] + 86399;
											$dids[$dp]['did'] = substr($e164,2);
											$dp++;
											$dids[$dp]['start'] = $rs->fields['date_last_invoice'] - (MAX_INV_GEN_PERIOD*86400);
											$dids[$dp]['end']   = $rs->fields['date_next_invoice'] + 86399;
											$dids[$dp]['did'] = substr($e164,1);
										}
									}
									echo "Found virtual number: ".$dids[$dp]['did']."\n";
								} # end test to see if truely virtual
							}
							$rs1->MoveNext();
						}
					}
	
					# Load rating table configuration
					$rate = $this->load_rating_table($db, $rs->fields['product_id']);
					if (is_array($rate)) {
						if ($plugin['rate_accountcode']) {			
							# rate accountcode based
							# echo "Rate by account code: ".$rs->fields['username']."\n";
							$dids[$dp]['accountcode'] = $rs->fields['username'];
							$this->rate_calls($db, $db, $dids, $rs->fields);
						} else {			
							# rate non-accountcode based
							$this->rate_calls($db, $db, $dids, $rs->fields);
						}	
					}
				}
			} # end did length check
			$rs->MoveNext();
		}
		$debug = ob_get_contents();
		echo $debug;
		ob_end_clean();
		if (defined('RATING_DEBUG')) {
			mail("joe@thrallingpenguin.com","Rating Debug For ".URL,$debug);
		}
		return true;
	}	
	
	/** Returns the call type of two DIDs. Returns: 1=INTRASTATE,2=INTERSTATE,3=TOLL-FREE
	 *  @param $src Source telephone number
	 *  @param $dst Destination telephone number
	 */
	function isIntrastateCall($src,$dst) 
	{
		# This is OHIOs NPAs. We need to know the location of us and we can load these values from
		# the beastly add-on table we have.
		#
		# Can we assume the business state is the location of this stuff?
		#
	    $ohio = $this->voip_intrastate;
	    if (strncmp("1800",$dst,4)==0 || strncmp("1877",$dst,4)==0 || strncmp("1866",$dst,4)==0 || strncmp("1888",$dst,4)==0)
	        return 3;
	    $s = 0; $d = 0;
	    foreach ($ohio as $p) {
	        if (strncmp("1".$p,$src,4)==0)
	            $s = 1;
	        if (strncmp("1".$p,$dst,4)==0)
	            $d = 1;
	    }
	    if($s == 1 && $d == 1)  return 1;
	    return 2;
	}

	/** Loads the rate table associated with a product.
	 *  @param $db A database connection handle
	 *  @param $product_id The ID of the product to load.
	 */
	function load_rating_table(&$db, $product_id)
	{
		global $rate;

		$sql = "SELECT b.id, b.connect_fee, b.increment_seconds, b.amount, b.pattern, b.type, b.direction, b.min, b.max, b.combine, b.percall as perCall, b.seconds_included, b.name FROM ".AGILE_DB_PREFIX."voip_rate_prod a inner join ".AGILE_DB_PREFIX."voip_rate b on (a.voip_rate_id=b.id and a.site_id=".DEFAULT_SITE.") WHERE product_id=".$product_id." ORDER BY type, direction DESC, length(pattern) DESC";
		echo $sql."\n";
		$rs = $db->Execute($sql); 
		unset($rate); 
		$i = 0;
		while (!$rs->EOF) {
			$rate[$i] = $rs->fields;
			$rate[$i]['pattern'] = str_replace("\r","",str_replace("\n","",ereg_replace("[^0-9;]","",$rs->fields['pattern'])));
			$rate[$i]['perCall'] = intval($rs->fields['perCall']);
			switch ($rs->fields['type']) {
			case 0:
				$rate[$i]['type'] = 'innetwork';
				break;
			case 1:
				$rate[$i]['type'] = 'local';
				break;
			case 2:
				$rate[$i]['type'] = 'regular';
				break;
			case 3:
				$rate[$i]['type'] = 'default';
				break;
			default:
				break;
			}
			switch ($rs->fields['direction']) {
			case 0:
				$rate[$i]['direction'] = 'inbound';
				break;
			case 1:
				$rate[$i]['direction'] = 'outbound';
				break;
			case 2:
				$rate[$i]['direction'] = 'both';
				break;
			default:
				break;
			}
			$i++;
			$rs->MoveNext();
		}
		if ($i == 0) {
        	#global $C_debug;
        	#$C_debug->error('voip.inc.php','load_rating_table','Rate table is empty for product_id = '.$product_id);			
			echo "Rating table is blank!\n\n";
		}
		return (isset($rate) ? $rate : false);
	}

	/** Returns boolean if the DID S is in the array of DIDs DIDS.
	 */
	function in_did_array($s, &$dids)
	{
		$ret = false;
		foreach ($dids as $d) {
			if (isset($d['did'])) {
				if ($d['did'] == $s) {
					$ret = true;
					break;
				}
			}
		}
		return $ret;
	}

	function price_call(&$dbast, $r, $dur, $callSQL, &$unit, &$quan)
	{
		$billedDur = (($dur - $r['seconds_included']) / $r['increment_seconds']);
		if ($billedDur < 0)
			$billedDur = 0;
		$billedDur = ceil($billedDur);
		$billedDur = ($billedDur * $r['increment_seconds'])/60;
		$cost = 0;
		$quan = $billedDur;
	
		# get count for min/max, honor the combine flag!
		$count = 0;
		if ($r['combine']) {
			$sql = "select sum(adjbillinterval) from ".AGILE_DB_PREFIX."voip_cdr where voip_rate_id=".$r['id']." and $callSQL";
			#echo $sql."\n";
			$rst = $dbast->Execute($sql);
			$count = intval($rst->fields[0]);
		}

		echo "count=$count, rmin=".$r['min'].", rmax=".$r['max']."\n";	
		if ($count >= $r['min'] && 
			($count <= $r['max'] || $r['max'] == -1)) {
			echo "perCall = ".$r['perCall']."\n";
			if ($r['perCall']) {
				$cost = $r['amount'];
				$quan = 1;
				$unit = $r['amount'];
			} else {
				$cost = ($r['amount'] * $billedDur);
				$quan = $billedDur;
				$unit = $r['amount'];
				echo "billedDur=".$billedDur."\n";
			}
		}
		print "cost=$cost, quan=$quan, unit=$unit\n";	
		return $cost;
	}

	/** Determines if DST is in SRC's local calling area.
	 */
	function is_local_calling_area(&$dbast, $src, $dst)
	{
		$cc1 = ""; $npa1 = ""; $nxx1 = "";
		$cc2 = ""; $npa2 = ""; $nxx2 = "";
		$e164 = "";
		if ($this->e164($src,$e164,$cc1,$npa1,$nxx1)) {
			if ($this->e164($dst,$e164,$cc2,$npa2,$nxx2)) {
				$sql = "select t1.grouping as id1, t2.grouping from ".AGILE_DB_PREFIX."voip_local_lookup as t1 join ".AGILE_DB_PREFIX."voip_local_lookup as t2 on (t1.npa='$npa1' and t1.nxx='$nxx1' and t2.npa='$npa2' and t2.nxx='$nxx2') where t1.grouping=t2.grouping";
				echo $sql."\n";
				$rs = $dbast->Execute($sql);
				if ($rs->fields[0] > 0) {
					return true;
				}
			}
		}
		return false;
	}

	function rate_calls(&$db, &$dbast, $dids, $crow, $postCharges = true)
	{
		global $rate;
		$quan = 0;
		$bAccountcode = false;
		
		# probably should have the e.164 values here too.
		$sql = "(";
		foreach($dids as $d) {
			if (strlen(@$d['accountcode'])) {
				$sql .= "(accountcode=".$db->qstr($d['accountcode'])." and date_orig>='".$d['start']."' and date_orig<='".$d['end']."') OR ";
				$bAccountcode = true;
			} else {
				$sql .= "((src='".$d['did']."' or dst='".$d['did']."') and date_orig>='".$d['start']."' and date_orig<='".$d['end']."') OR ";
			}
		}
		$sql = substr($sql,0,strlen($sql)-3).")";
		$callSQL = $sql;
		$sql = "select * from ".AGILE_DB_PREFIX."voip_cdr where $sql and
			(lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') 
			AND disposition='ANSWERED' and (rated=2)";
		echo $sql."\n";
		$rs1 = $dbast->Execute($sql);
		#print_r($rate);
		while ($rs1 && !$rs1->EOF) {
			
			$calltype = 0;
			if(strlen($this->voip_intrastate))
				$calltype = $this->isIntrastateCall($rs1->fields['src'], $rs1->fields['dst']);
			$slotMatched = 0; $unit = 0; $quan = 0; $amount = 0;
			$isInbound = $this->in_did_array($rs1->fields['dst'], $dids);
			if ($isInbound)
				$calltype = 4;
				
			$slotMatched = 0; $unit = 0; $quan = 0;
			foreach ($rate as $r) {
				if ($r['type'] == 'innetwork') {
					#echo "Is the call In Network?\n";
					if ($r['direction'] == 'inbound') {
						$search = $rs1->fields['src'];
					} else if ($r['direction'] == 'outbound') {
						$search = $rs1->fields['dst'];
					} else {
						$search = $rs1->fields['src']."','".$rs1->fields['dst'];
					}
					$sql = "select count(*) from ".AGILE_DB_PREFIX."voip_in_network where did in ('".$search."')";
					echo $sql."\n";
					$rs2 = $dbast->Execute($sql);
					if ($rs2->fields[0] > 0) {
						echo "Yes, call is in-network.\n";
						$amount = 0;
						$slotMatched = $r['id'];
						#break;
					}
				} else if ($r['type'] == 'local') {
					echo "Local calling area\n";
					if ($this->is_local_calling_area($dbast, $rs1->fields['src'], $rs1->fields['dst'])) {
						$amount = 0;
						$slotMatched = $r['id'];
						#break;
					}
				} else if ($r['type'] == 'regular' || $r['type'] == 'default') {
					#echo "src=".$rs1->fields['src']."\n";
					#echo "dst=".$rs1->fields['dst']."\n";
					$pats = split(";", $r['pattern']);
					$search = $rs1->fields['dst'];
					foreach ($pats as $pattern) {
						#echo "Matching against: $pattern\n";
						#if (ereg("^".$pattern, $search) || $r['type'] == 'default') {
						if ((strncmp($pattern, $search, strlen($pattern))==0 && strlen($pattern)) || $r['type'] == 'default') {
							echo "src=".$rs1->fields['src']."\n";
							echo "dst=".$rs1->fields['dst']."\n";
							echo "pattern=".$pattern."\n";
							echo "Billsec=".$rs1->fields['billsec']."\n";
							$amount = $this->price_call($db, $r, $rs1->fields['billsec'], $callSQL, $unit, $quan);
							$slotMatched = $r['id'];
							echo "Matched slot ".$r['id']." (".$r['name'].") with quan=$quan and unit=$unit\n";
							break;
						}
					}
				}
				# if the slot matched, and the call direction matches, then exit the rating table matching
				if ($slotMatched && (
					$r['direction'] == 'both' ||					 # rate entry for both call directions
					($r['direction'] == 'inbound' && $isInbound) ||  # inbound rate and inbound call direction
					($r['direction'] == 'outbound' && !$isInbound) ) # outbound rate and outbound call direction 
				) {
					echo "Found match: ".$slotMatched."\n";
					break;
				} else if ($slotMatched && $bAccountcode ) {
					# matched via account codes
					echo "Found match via accountcode: ".$slotMatched."\n";
					break;
				} else if ($slotMatched) {
					# reset the variables and continue trying to find a match
					$slotMatched = 0; $unit = 0; $quan = 0; $amount = 0;
					echo "Incorrect direction, continuing to match...\n";
				}
			}
	
			$sql = "update ".AGILE_DB_PREFIX."voip_cdr set account_id=".$db->qstr($crow['account_id']).", amount='".$amount."', calltype='$calltype', voip_rate_id='$slotMatched', rated=1, adjbillinterval=".$db->qstr($quan)." WHERE id=".$rs1->fields['id']." AND site_id=".DEFAULT_SITE;
			echo $sql."\n";
			if (!$db->Execute($sql)) {
				echo $db->ErrorMsg()."\n";
			}

			if ($unit && $postCharges) {
				$a = 'Source=='.$rs1->fields['src'].'\r\nDestination=='.$rs1->fields['dst'];
				$a .= '\r\nvoip_cdr_id=='.$rs1->fields['id'].'\r\ndate_orig=='.$rs1->fields['date_orig'];
				if ($r['connect_fee']) {
					$b = $a.'\r\nConnection Charge';
                    unset($fields);
                    $fields['date_orig'] = $rs1->fields['date_orig'];
                    $fields['status'] = 0;
                    $fields['service_id'] = $crow['service_id'];
                    $fields['amount'] = $r['connect_fee'];
                    $fields['sweep_type'] = 6;
                    $fields['taxable'] = 0;
                    $fields['quantity'] = 1;
                    $fields['product_id'] = $crow['product_id'];
                    $fields['attributes'] = $b;
                    $db->Execute( sqlInsert($db,"charge",$fields) );
				}
				if ($quan > 0) {
					unset($fields);
					$fields['date_orig'] = $rs1->fields['date_orig'];
					$fields['status'] = 0;
					$fields['service_id'] = $crow['service_id'];
					$fields['amount'] = $unit;
					$fields['sweep_type'] = 6;
					$fields['taxable'] = 0;
					$fields['quantity'] = $quan;
					$fields['product_id'] = $crow['product_id'];
					$fields['attributes'] = $a;
					$db->Execute( sqlInsert($db,"charge",$fields) );
				}
			}
			
			$rs1->MoveNext();
		}
	}

	function microtime_float()
	{
	   list($usec, $sec) = explode(" ", microtime());
	   return ((float)$usec + (float)$sec);
	}

/**
 * TODO: Accountcode based rating, Prepaid based rating
 */ 
	function c_task($VAR)
	{
		# set the PHP timeout and the don't abort flag
		set_time_limit(60 * 15);
		
		# normalize the CDR records
		if($this->perform_normalization) {
			echo "Begin normalization...\n";
			$this->normalize($db);
			echo "Finished normalization...\n";
		}
				
		# Add in the prepaid rating pieces

		# Begin the postpaid rating
		$bDoCallType = false;
		if(strlen($this->voip_intrastate))
			$bDoCallType = true;
		
		$db =& DB();	
		$sql = "select * from ".AGILE_DB_PREFIX."voip_cdr where 
		(lastapp='Dial' or lastapp='VoiceMail' or lastapp='MeetMe' or lastapp='Hangup') 
		AND disposition='ANSWERED' and rated=2 limit 1000";
		$rs = $db->Execute($sql); $i = 0; $total = 0.0;
		$st = $this->microtime_float();
		while(!$rs->EOF) {
			unset($match);
			# who does the number belong to?
			$account_id = 0; $service_id = 0; $product_id = 0; $callSQL = "";
			find_owner($rs->fields['src'], $account_id, $service_id, $product_id, $callSQL);
			$isInbound = 0;
			$calltype = 0;
			if($bDoCallType)
				$calltype = $this->isIntrastateCall($rs->fields['src'], $rs->fields['dst']);			
			#echo "Account: {$account_id} on src\n";
			if($account_id === false) {
				find_owner($rs->fields['dst'], $account_id, $service_id, $product_id, $callSQL);
				$isInbound = 1;
				$calltype=4;
				#echo "AccountL {$account_id} on dst\n";
			}
			
			if($account_id !== false) {
				# echo "Account=$account_id Product=$product_id Service=$service_id<br />";
				# Retrieve the correct rate table
				$rt =& $this->c_load_rating_table($db, $product_id);
				
				if(is_resource($rt)) {
					# Rate the call
					$src = $isInbound ? $rs->fields['dst'] : $rs->fields['src'];
					$dst = $isInbound ? $rs->fields['src'] : $rs->fields['dst'];
					if($match=agileco_search_rate_table($rt, strval($dst), intval($rs->fields['billsec']),
						intval($isInbound), strval($callSQL))) {
						#echo "<pre>";
						#echo "SRC=".$src."\n";
						#echo "DST=".strval($dst)."\n";
						#echo "BILLSEC=".intval($rs->fields['billsec'])."\n";
						#echo "In Bound?=".intval($isInbound)."\n";
						#echo "Call Type=".$calltype."\n";
						#echo "callSQL=".$callSQL."\n\n";
						#echo print_r($match,true);
						#echo "\n";
						#echo "</pre>";
					} else {
						#echo "SRC=".$src."\n";
						#echo "DST=".strval($dst)."\n";
						#echo "BILLSEC=".intval($rs->fields['billsec'])."\n";
						#echo "In Bound?=".intval($isInbound)."\n";
						#echo "Call Type=".$calltype."\n";
						#echo 'Returned false.'."\n\n";
						$match['amount'] = 0;
						$match['quantity'] = 0;
						$match['unit'] = 0;
						$match['voip_rate_id'] = 0;
					}
					$rated=1;
				} else {
					echo "Product $product_id does not have a rating table.\n";
				}
			} else {
				$isInbound = 0; $account_id = 0; $calltype=0; $rated = 3;
			}
			if(isset($match)) {
				$total += $match['amount'];
				$sql = "update ".AGILE_DB_PREFIX."voip_cdr SET 
					account_id=".$db->qstr($account_id).", 
					amount=".$db->qstr($match['amount']).", 
					calltype=".$db->qstr($calltype).", 
					voip_rate_id=".$db->qstr($match['voip_rate_id']).", 
					rated={$rated}, 
					adjbillinterval=".$db->qstr($match['quantity']).",
					site_id=".DEFAULT_SITE."  
					WHERE id=".$rs->fields['id']; 
				#echo $sql."\n";
				if (!$db->Execute($sql)) {
					echo $sql."\n";
					echo $db->ErrorMsg()."\n";
				}
				$a = 'Source=='.$rs->fields['src'].'\r\nDestination=='.$rs->fields['dst'];
				$a .= '\r\nvoip_cdr_id=='.$rs->fields['id'].'\r\ndate_orig=='.$rs->fields['date_orig'];
				if (isset($match['connect_fee']) && $match['connect_fee']) {
					$b = $a.'\r\nConnection Charge';
					$cid = sqlGenID($db, "charge");
					$sql = "INSERT INTO ".AGILE_DB_PREFIX."charge SET
						id=".$db->qstr($cid).",
						side_id=".DEFAULT_SITE.",
						date_orig=.".$db->qstr($rs->fields['date_orig']).",
						status=0,
						sweep_type=6,
						product_id=".$db->qstr($product_id).",
						service_id=".$db->qstr($service_id).",
						amount=".$db->qstr($match['connect_fee']).",
						quantity=1,
						taxable=0,
						attributes=".$db->qstr($b);
					if (!$db->Execute($sql)) {
						echo $sql."\n";
						echo $db->ErrorMsg()."\n";
					}
				}
				if ($match['quantity'] > 0) {
					$cid = sqlGenID($db, "charge");
					$sql = "INSERT INTO ".AGILE_DB_PREFIX."charge SET
					id=".$db->qstr($cid).",
					site_id=".DEFAULT_SITE.",
					date_orig=".$db->qstr($rs->fields['date_orig']).",
					status=0,
					sweep_type=6,
					product_id=".$db->qstr($product_id).",
					service_id=".$db->qstr($service_id).",
					amount=".$db->qstr($match['unit']).",
					quantity=".$db->qstr($match['quantity']).",
					taxable=0,
					attributes=".$db->qstr($a);
					if (!$db->Execute($sql)) {
						echo $sql."\n";
						echo $db->ErrorMsg()."\n";
					}
				}
			}
			$i++;
			$rs->MoveNext();
		}
		$et = $this->microtime_float();
		$tt = $et - $st;
		echo "Rated {$i} entries in {$tt} seconds.<br><br>";
		echo "Cough up $".number_format($total,4)."!<br>";	
	}
	
	/** Loads the rate table associated with a product.
	 *  @param $db A database connection handle
	 *  @param $product_id The ID of the product to load.
	 */
	function & c_load_rating_table(&$db, $product_id)
	{
		static $rate_cache;

		/* Is the rate already cached? */
		if(isset($rate_cache[$product_id])) {
			return $rate_cache[$product_id];
		}
		
		/* Cache Miss. Generate the entry. */
		$db =& DB();
		$rate_cache[$product_id] = agileco_new_rate_table();
		$i = 0; $st = $this->microtime_float();
		$sql = "SELECT b.id, b.connect_fee, b.increment_seconds, b.amount, b.pattern, b.type, b.direction, b.min, b.max, b.combine, b.percall, b.seconds_included FROM ".AGILE_DB_PREFIX."voip_rate_prod a left join ".AGILE_DB_PREFIX."voip_rate b on (a.voip_rate_id=b.id and a.site_id=".DEFAULT_SITE.") WHERE product_id=".$product_id." ORDER BY type ASC";
		#echo $sql."\n";
		$rs = $db->Execute($sql); 
		while (!$rs->EOF) {
			$pattern = preg_replace("/[^0-9;]/","",$rs->fields['pattern']);
			$pattern = str_replace("\r","",str_replace("\n","",$pattern));
			$pats = explode(";",$pattern);
			foreach($pats as $pat) {
				agileco_rate_table_insert(
					$rate_cache[$product_id],
					$pat,
					intval($rs->fields['id']),
					floatval($rs->fields['connect_fee']),
					intval($rs->fields['increment_seconds']),
					intval($rs->fields['seconds_included']),
					floatval($rs->fields['amount']),
					intval($rs->fields['min']),
					intval($rs->fields['max']),
					intval($rs->fields['type']),
					intval($rs->fields['direction']),
					intval($rs->fields['combine']),
					intval($rs->fields['percall'])
				);
				++$i;
			}
			$rs->MoveNext();
		}
		$et = $this->microtime_float(); $tt = $et - $st;
		echo "Loaded {$i} patterns for product {$product_id} in {$tt} seconds.<br />\n";
		return $rate_cache[$product_id];
	}
	 
}

#
# C MODULE FUNCTIONS FOLLOW BELOW
#

// NOTE: I didn't include this in the class because it is known to be faster to call
//       a function, than a method of a class.
function find_owner($did, &$account_id, &$service_id, &$product_id, &$callSQL) {
	static $owners;
	
	if(isset($owners[$did])) {
		$account_id = $owners[$did]['account_id'];
		$service_id = $owners[$did]['service_id'];
		$product_id = $owners[$did]['product_id'];
		$callSQL = $owners[$did]['callSQL'];
		return true;
	}
	$db =& DB();
	#$sql = "SELECT id FROM ab_account WHERE username=".$db->qstr($did)." and site_id=".DEFAULT_SITE;
	$sql = "select A.account_id, A.service_id, B.product_id, B.date_last_invoice, B.date_next_invoice  
		from ".AGILE_DB_PREFIX."voip_did A inner join ".AGILE_DB_PREFIX."service B on (B.id=A.service_id) 
		where did=".$db->qstr($did)." and A.site_id=".DEFAULT_SITE." and B.site_id=".DEFAULT_SITE;
	#echo $sql."<br>";
	$row = $db->GetRow($sql);
	if($row === false || !isset($row[0])) {
		$account_id = false;
		$service_id = false;
		$product_id = false;
		$callSQL = "";
		return false;
	}
	$account_id = $row[0];
	$service_id = $row[1];
	$product_id = $row[2];

	$row[3] = $row[3] - (MAX_INV_GEN_PERIOD*86400);
	$row[4] = $row[4] + 86399;
										
	$callSQL = "((src=".$db->qstr($did)." or dst=".$db->qstr($did).") and ";
	$callSQL .= "date_orig>=".$db->qstr($row[3])." and date_orig<=".$db->qstr($row[4]).")";
	
	$owners[$did]['account_id'] = $row[0];
	$owners[$did]['service_id'] = $row[1];
	$owners[$did]['product_id'] = $row[2];
	$owners[$did]['callSQL'] = $callSQL;
	return true;
}

function agileco_php_in_network($did) {
	$db =& DB();
	$sql = "select count(*) from ".AGILE_DB_PREFIX."voip_in_network where did in ('".$did."')";
	#echo $sql."\n";
	$rs2 = $db->Execute($sql);
	if ($rs2->fields[0] > 0) {
		#echo "Yes, call is local.\n";
		return 1;
	}
	return 0;
}

function agileco_php_local_call($dst) {
	global $src;
	
	$voip = new voip;
	$cc1 = ""; $npa1 = ""; $nxx1 = "";
	$cc2 = ""; $npa2 = ""; $nxx2 = "";
	$e164 = "";
	if ($voip->e164($src,$e164,$cc1,$npa1,$nxx1)) {
		if ($voip->e164($dst,$e164,$cc2,$npa2,$nxx2)) {
			$sql = "select t1.grouping as id1, t2.grouping from ".AGILE_DB_PREFIX."voip_local_lookup as t1 join ".AGILE_DB_PREFIX."voip_local_lookup as t2 on (t1.npa='$npa1' and t1.nxx='$nxx1' and t2.npa='$npa2' and t2.nxx='$nxx2') where t1.grouping=t2.grouping";
			echo $sql."\n";
			$rs = $db->Execute($sql);
			if ($rs->fields[0] > 0) {
				return 1;
			}
		}
	}
	return 0;
}

function agileco_php_minutes_used($voip_did_id, $callSQL) {
	$db =& DB();
	$sql = "select sum(adjbillinterval) from ".AGILE_DB_PREFIX."voip_cdr where voip_rate_id={$voip_did_id} and {$callSQL}";
	$rst = $db->Execute($sql);
	$num = intval($rst->fields[0]);
	echo 'Customer has '.$num.' minutes used.<br>';
	return $num;
}

?>