Portions created by the Initial Developer are Copyright (C) 2016 the Initial Developer. All Rights Reserved. Contributor(s): Mark J Crane */ /** * xml_cdr class provides methods for adding cdr records to the database * * @method boolean add */ if (!class_exists('xml_cdr')) { class xml_cdr { //define variables public $db; public $array; public $debug; public $fields; //user summary public $domain_uuid; public $quick_select; public $start_stamp_begin; public $start_stamp_end; public $include_internal; public $extensions; /** * Called when the object is created */ public function __construct() { //connect to the database if not connected if (!$this->db) { require_once "resources/classes/database.php"; $database = new database; $database->connect(); $this->db = $database->db; } } /** * Called when there are no references to a particular object * unset the variables used in the class */ public function __destruct() { if (isset($this)) foreach ($this as $key => $value) { unset($this->$key); } } /** * cdr process logging */ public function log($message) { //save to file system (alternative to a syslog server) $fp = fopen($_SESSION['server']['temp']['dir'].'/xml_cdr.log', 'a+'); if (!$fp) { return; } fwrite($fp, $message); fclose($fp); } /** * cdr fields in the database schema */ public function fields() { $this->fields[] = "uuid"; $this->fields[] = "domain_uuid"; $this->fields[] = "extension_uuid"; $this->fields[] = "domain_name"; $this->fields[] = "accountcode"; $this->fields[] = "direction"; $this->fields[] = "default_language"; $this->fields[] = "context"; $this->fields[] = "xml"; $this->fields[] = "json"; $this->fields[] = "caller_id_name"; $this->fields[] = "caller_id_number"; $this->fields[] = "destination_number"; $this->fields[] = "source_number"; $this->fields[] = "start_epoch"; $this->fields[] = "start_stamp"; $this->fields[] = "answer_stamp"; $this->fields[] = "answer_epoch"; $this->fields[] = "end_epoch"; $this->fields[] = "end_stamp"; $this->fields[] = "duration"; $this->fields[] = "mduration"; $this->fields[] = "billsec"; $this->fields[] = "billmsec"; $this->fields[] = "bridge_uuid"; $this->fields[] = "read_codec"; $this->fields[] = "read_rate"; $this->fields[] = "write_codec"; $this->fields[] = "write_rate"; $this->fields[] = "remote_media_ip"; $this->fields[] = "network_addr"; $this->fields[] = "recording_file"; $this->fields[] = "leg"; $this->fields[] = "pdd_ms"; $this->fields[] = "rtp_audio_in_mos"; $this->fields[] = "last_app"; $this->fields[] = "last_arg"; $this->fields[] = "cc_side"; $this->fields[] = "cc_member_uuid"; $this->fields[] = "cc_queue_joined_epoch"; $this->fields[] = "cc_queue"; $this->fields[] = "cc_member_session_uuid"; $this->fields[] = "cc_agent"; $this->fields[] = "cc_agent_type"; $this->fields[] = "waitsec"; $this->fields[] = "conference_name"; $this->fields[] = "conference_uuid"; $this->fields[] = "conference_member_id"; $this->fields[] = "digits_dialed"; $this->fields[] = "pin_number"; $this->fields[] = "hangup_cause"; $this->fields[] = "hangup_cause_q850"; $this->fields[] = "sip_hangup_disposition"; } /** * save to the database */ public function save() { $this->fields(); $field_count = sizeof($this->fields); $sql = "insert into v_xml_cdr ("; $f = 1; if (isset($this->fields)) foreach ($this->fields as $field) { if ($field_count == $f) { $sql .= "$field "; } else { $sql .= "$field, "; } $f++; } $sql .= ")\n"; $sql .= "values \n"; $row_count = sizeof($this->array); //$field_count = sizeof($this->fields); $i = 0; if (isset($this->array)) foreach ($this->array as $row) { $sql .= "("; $f = 1; if (isset($this->fields)) foreach ($this->fields as $field) { if (isset($row[$field]) && strlen($row[$field]) > 0) { $sql .= "'".$row[$field]."'"; } else { $sql .= "null"; } if ($field_count != $f) { $sql .= ","; } $f++; } $sql .= ")"; if ($row_count != $i) { $sql .= ",\n"; } $i++; } if (substr($sql,-2) == ",\n") { $sql = substr($sql,0,-2); } $this->db->exec(check_sql($sql)); unset($sql); } /** * process method converts the xml cdr and adds it to the database */ public function xml_array($row, $leg, $xml_string) { //fix the xml by escaping the contents of if(defined('STDIN')) { $xml_string = preg_replace_callback("/<([^><]+)>(.*?[><].*?)<\/\g1>/", function ($matches) { return '<' . $matches[1] . '>' . str_replace(">", ">", str_replace("<", "<", $matches[2]) ) . ''; }, $xml_string ); } //parse the xml to get the call detail record info try { //$this->log($xml_string); $xml = simplexml_load_string($xml_string); //$this->log("\nxml load done\n"); } catch(Exception $e) { echo $e->getMessage(); //$this->log("\nfail loadxml: " . $e->getMessage() . "\n"); } //get the destination number if ($xml->variables->current_application == "bridge") { $current_application_data = urldecode($xml->variables->current_application_data); $bridge_array = explode("/", $current_application_data); $destination_number = end($bridge_array); if (strpos($destination_number,'@') !== FALSE) { $destination_array = explode("@", $destination_number); $destination_number = $destination_array[0]; } } else { $destination_number = urldecode($xml->variables->sip_to_user); } //if last_sent_callee_id_number is set use it for the destination_number if (strlen($xml->variables->last_sent_callee_id_number) > 0) { $destination_number = urldecode($xml->variables->last_sent_callee_id_number); } //get the caller id $caller_id_name = urldecode($xml->variables->effective_caller_id_name); $caller_id_number = urldecode($xml->variables->effective_caller_id_number); if (strlen($caller_id_number) == 0) foreach ($xml->callflow as $row) { $caller_id_name = urldecode($row->caller_profile->caller_id_name); $caller_id_number = urldecode($row->caller_profile->caller_id_number); } //misc $uuid = check_str(urldecode($xml->variables->uuid)); $this->array[$row]['uuid'] = $uuid; $this->array[$row]['destination_number'] = check_str($destination_number); $this->array[$row]['source_number'] = check_str(urldecode($xml->variables->effective_caller_id_number)); $this->array[$row]['user_context'] = check_str(urldecode($xml->variables->user_context)); $this->array[$row]['network_addr'] = check_str(urldecode($xml->variables->sip_network_ip)); $this->array[$row]['caller_id_name'] = check_str($caller_id_name); $this->array[$row]['caller_id_number'] = check_str($caller_id_number); $this->array[$row]['accountcode'] = check_str(urldecode($xml->variables->accountcode)); $this->array[$row]['default_language'] = check_str(urldecode($xml->variables->default_language)); $this->array[$row]['bridge_uuid'] = check_str(urldecode($xml->variables->bridge_uuid)); //$this->array[$row]['digits_dialed'] = check_str(urldecode($xml->variables->digits_dialed)); $this->array[$row]['sip_hangup_disposition'] = check_str(urldecode($xml->variables->sip_hangup_disposition)); $this->array[$row]['pin_number'] = check_str(urldecode($xml->variables->pin_number)); //time $this->array[$row]['start_epoch'] = check_str(urldecode($xml->variables->start_epoch)); $start_stamp = check_str(urldecode($xml->variables->start_stamp)); $this->array[$row]['start_stamp'] = $start_stamp; $this->array[$row]['answer_stamp'] = check_str(urldecode($xml->variables->answer_stamp)); $this->array[$row]['answer_epoch'] = check_str(urldecode($xml->variables->answer_epoch)); $this->array[$row]['end_epoch'] = check_str(urldecode($xml->variables->end_epoch)); $this->array[$row]['end_stamp'] = check_str(urldecode($xml->variables->end_stamp)); $this->array[$row]['duration'] = check_str(urldecode($xml->variables->duration)); $this->array[$row]['mduration'] = check_str(urldecode($xml->variables->mduration)); $this->array[$row]['billsec'] = check_str(urldecode($xml->variables->billsec)); $this->array[$row]['billmsec'] = check_str(urldecode($xml->variables->billmsec)); //codecs $this->array[$row]['read_codec'] = check_str(urldecode($xml->variables->read_codec)); $this->array[$row]['read_rate'] = check_str(urldecode($xml->variables->read_rate)); $this->array[$row]['write_codec'] = check_str(urldecode($xml->variables->write_codec)); $this->array[$row]['write_rate'] = check_str(urldecode($xml->variables->write_rate)); $this->array[$row]['remote_media_ip'] = check_str(urldecode($xml->variables->remote_media_ip)); $this->array[$row]['hangup_cause'] = check_str(urldecode($xml->variables->hangup_cause)); $this->array[$row]['hangup_cause_q850'] = check_str(urldecode($xml->variables->hangup_cause_q850)); //call center $this->array[$row]['cc_side'] = check_str(urldecode($xml->variables->cc_side)); $this->array[$row]['cc_member_uuid'] = check_str(urldecode($xml->variables->cc_member_uuid)); $this->array[$row]['cc_queue_joined_epoch'] = check_str(urldecode($xml->variables->cc_queue_joined_epoch)); $this->array[$row]['cc_queue'] = check_str(urldecode($xml->variables->cc_queue)); $this->array[$row]['cc_member_session_uuid'] = check_str(urldecode($xml->variables->cc_member_session_uuid)); $this->array[$row]['cc_agent'] = check_str(urldecode($xml->variables->cc_agent)); $this->array[$row]['cc_agent_type'] = check_str(urldecode($xml->variables->cc_agent_type)); $this->array[$row]['waitsec'] = check_str(urldecode($xml->variables->waitsec)); //app info $this->array[$row]['last_app'] = check_str(urldecode($xml->variables->last_app)); $this->array[$row]['last_arg'] = check_str(urldecode($xml->variables->last_arg)); //conference $this->array[$row]['conference_name'] = check_str(urldecode($xml->variables->conference_name)); $this->array[$row]['conference_uuid'] = check_str(urldecode($xml->variables->conference_uuid)); $this->array[$row]['conference_member_id'] = check_str(urldecode($xml->variables->conference_member_id)); //call quality $rtp_audio_in_mos = check_str(urldecode($xml->variables->rtp_audio_in_mos)); if (strlen($rtp_audio_in_mos) > 0) { $this->array[$row]['rtp_audio_in_mos'] = $rtp_audio_in_mos; } //store the call leg $this->array[$row]['leg'] = $leg; //store the call direction $this->array[$row]['direction'] = check_str(urldecode($xml->variables->call_direction)); //store post dial delay, in milliseconds $this->array[$row]['pdd_ms'] = check_str(urldecode($xml->variables->progress_mediamsec) + urldecode($xml->variables->progressmsec)); //get break down the date to year, month and day $tmp_time = strtotime($start_stamp); $tmp_year = date("Y", $tmp_time); $tmp_month = date("M", $tmp_time); $tmp_day = date("d", $tmp_time); //get the domain values from the xml $domain_name = check_str(urldecode($xml->variables->domain_name)); $domain_uuid = check_str(urldecode($xml->variables->domain_uuid)); //get the domain name from sip_req_host if (strlen($domain_name) == 0) { $domain_name = check_str(urldecode($xml->variables->sip_req_host)); } //send the domain name to the cdr log //$this->log("\ndomain_name is `$domain_name`; domain_uuid is '$domain_uuid'\n"); //get the domain_uuid with the domain_name if (strlen($domain_uuid) == 0) { $sql = "select domain_uuid from v_domains "; if (strlen($domain_name) == 0 && $context != 'public' && $context != 'default') { $sql .= "where domain_name = '".$context."' "; } else { $sql .= "where domain_name = '".$domain_name."' "; } $row = $this->db->query($sql)->fetch(); $domain_uuid = $row['domain_uuid']; } //set values in the database if (strlen($domain_uuid) > 0) { $this->array[$row]['domain_uuid'] = $domain_uuid; } if (strlen($domain_name) > 0) { $this->array[$row]['domain_name'] = $domain_name; } //check whether a recording exists $recording_relative_path = '/'.$_SESSION['domain_name'].'/archive/'.$tmp_year.'/'.$tmp_month.'/'.$tmp_day; if (file_exists($_SESSION['switch']['recordings']['dir'].$recording_relative_path.'/'.$uuid.'.wav')) { $recording_file = $recording_relative_path.'/'.$uuid.'.wav'; } elseif (file_exists($_SESSION['switch']['recordings']['dir'].$recording_relative_path.'/'.$uuid.'.mp3')) { $recording_file = $recording_relative_path.'/'.$uuid.'.mp3'; } if(isset($recording_file) && !empty($recording_file)) { $this->array[$row]['recording_file'] = $recording_file; } //save to the database in xml format if ($_SESSION['cdr']['format']['text'] == "xml" && $_SESSION['cdr']['storage']['text'] == "db") { $this->array[$row]['xml'] = check_str($xml_string); } //save to the database in json format if ($_SESSION['cdr']['format']['text'] == "json" && $_SESSION['cdr']['storage']['text'] == "db") { $this->array[$row]['json'] = check_str(json_encode($xml)); } //insert the check_str($extension_uuid) if (strlen($xml->variables->extension_uuid) > 0) { $this->array[$row]['extension_uuid'] = check_str(urldecode($xml->variables->extension_uuid)); } //insert the values if (strlen($uuid) > 0) { if ($this->debug) { //$time5_insert = microtime(true); //echo $sql."
\n"; } try { $error = "false"; //$this->db->exec(check_sql($sql)); } catch(PDOException $e) { $tmp_dir = $_SESSION['switch']['log']['dir'].'/xml_cdr/failed/'; if(!file_exists($tmp_dir)) { mkdir($tmp_dir, 02770, true); } if ($_SESSION['cdr']['format']['text'] == "xml") { $tmp_file = $uuid.'.xml'; $fh = fopen($tmp_dir.'/'.$tmp_file, 'w'); fwrite($fh, $xml_string); } else { $tmp_file = $uuid.'.json'; $fh = fopen($tmp_dir.'/'.$tmp_file, 'w'); fwrite($fh, json_encode($xml)); } fclose($fh); if ($this->debug) { echo $e->getMessage(); } $error = "true"; } if ($_SESSION['cdr']['storage']['text'] == "dir" && $error != "true") { if (strlen($uuid) > 0) { $tmp_time = strtotime($start_stamp); $tmp_year = date("Y", $tmp_time); $tmp_month = date("M", $tmp_time); $tmp_day = date("d", $tmp_time); $tmp_dir = $_SESSION['switch']['log']['dir'].'/xml_cdr/archive/'.$tmp_year.'/'.$tmp_month.'/'.$tmp_day; if(!file_exists($tmp_dir)) { mkdir($tmp_dir, 02770, true); } if ($_SESSION['cdr']['format']['text'] == "xml") { $tmp_file = $uuid.'.xml'; $fh = fopen($tmp_dir.'/'.$tmp_file, 'w'); fwrite($fh, $xml_string); } else { $tmp_file = $uuid.'.json'; $fh = fopen($tmp_dir.'/'.$tmp_file, 'w'); fwrite($fh, json_encode($xml)); } fclose($fh); } } unset($error); //if ($this->debug) { //GLOBAL $insert_time,$insert_count; //$insert_time+=microtime(true)-$time5_insert; //add this current query. //$insert_count++; //} } unset($sql); } /** * get xml from the filesystem and save it to the database */ public function read_files() { $xml_cdr_dir = $_SESSION['switch']['log']['dir'].'/xml_cdr'; $dir_handle = opendir($xml_cdr_dir); $x = 0; while($file = readdir($dir_handle)) { if ($file != '.' && $file != '..') { if ( !is_dir($xml_cdr_dir . '/' . $file) ) { //get the leg of the call and the file prefix if (substr($file, 0, 2) == "a_") { $leg = "a"; $file_prefix = substr($file, 2, 1); } else { $leg = "b"; $file_prefix = substr($file, 0, 1); } //set the limit if (isset($_SERVER["argv"][1]) && is_numeric($_SERVER["argv"][1])) { $limit = $_SERVER["argv"][1]; } else { $limit = 1; } //filter for specific files based on the file prefix if (isset($_SERVER["argv"][2])) { if (strpos($_SERVER["argv"][2], $file_prefix) !== FALSE) { $import = true; } else { $import = false; } } else { $import = true; } //import the call detail record if ($import) { //get the xml cdr string $xml_string = file_get_contents($xml_cdr_dir.'/'.$file); //parse the xml and insert the data into the db $this->xml_array($x, $leg, $xml_string); //delete the file after it has been imported unlink($xml_cdr_dir.'/'.$file); } //increment the value if ($import) { $x++; } //if limit exceeded exit the loop if ($limit == $x) { //echo "limit: $limit count: $x if\n"; break; } } } } $this->save(); closedir($dir_handle); } //$this->read_files(); /** * read the call detail records from the http post */ public function post() { if (isset($_POST["cdr"])) { //debug method if ($this->debug){ print_r($_POST["cdr"]); } //authentication for xml cdr http post if (!defined('STDIN')) { if ($_SESSION["cdr"]["http_enabled"]["boolean"] == "true" && strlen($_SESSION["xml_cdr"]["username"]) == 0) { //get the contents of xml_cdr.conf.xml $conf_xml_string = file_get_contents($_SESSION['switch']['conf']['dir'].'/autoload_configs/xml_cdr.conf.xml'); //parse the xml to get the call detail record info try { $conf_xml = simplexml_load_string($conf_xml_string); } catch(Exception $e) { echo $e->getMessage(); } if (isset($conf_xml->settings->param)) foreach ($conf_xml->settings->param as $row) { if ($row->attributes()->name == "cred") { $auth_array = explode(":", $row->attributes()->value); //echo "username: ".$auth_array[0]."
\n"; //echo "password: ".$auth_array[1]."
\n"; } if ($row->attributes()->name == "url") { //check name is equal to url } } } } //if http enabled is set to false then deny access if (!defined('STDIN')) { if ($_SESSION["cdr"]["http_enabled"]["boolean"] == "false") { echo "access denied
\n"; return; } } //check for the correct username and password if (!defined('STDIN')) { if ($_SESSION["cdr"]["http_enabled"]["boolean"] == "true") { if ($auth_array[0] == $_SERVER["PHP_AUTH_USER"] && $auth_array[1] == $_SERVER["PHP_AUTH_PW"]) { //echo "access granted
\n"; $_SESSION["xml_cdr"]["username"] = $auth_array[0]; $_SESSION["xml_cdr"]["password"] = $auth_array[1]; } else { echo "access denied
\n"; return; } } } //loop through all attribues //foreach($xml->settings->param[1]->attributes() as $a => $b) { // echo $a,'="',$b,"\"
\n"; //} //get the http post variable $xml_string = trim($_POST["cdr"]); //get the leg of the call if (substr($_REQUEST['uuid'], 0, 2) == "a_") { $leg = "a"; } else { $leg = "b"; } //log the xml cdr //xml_cdr_log("process cdr via post\n"); //parse the xml and insert the data into the database $this->xml_array(0, $leg, $xml_string); $this->save(); } } //$this->post(); /** * user summary returns an array */ public function user_summary() { //get current extension info $sql = "select "; $sql .= "domain_uuid, "; $sql .= "extension_uuid, "; $sql .= "extension, "; $sql .= "number_alias, "; $sql .= "description "; $sql .= "from "; $sql .= "v_extensions "; $sql .= "where "; $sql .= "enabled = 'true' "; if (!($_GET['showall'] == 'true' && permission_exists('xml_cdr_all'))) { $sql .= "and domain_uuid = '".$this->domain_uuid."' "; } if (!(if_group("admin") || if_group("superadmin"))) { if (count($_SESSION['user']['extension']) > 0) { $sql .= "and ("; $x = 0; foreach($_SESSION['user']['extension'] as $row) { if ($x > 0) { $sql .= "or "; } $sql .= "extension = '".$row['user']."' "; $x++; } $sql .= ")"; } else { //used to hide any results when a user has not been assigned an extension $sql .= "and extension = 'disabled' "; } } $sql .= "order by "; $sql .= "extension asc"; $prep_statement = $this->db->prepare(check_sql($sql)); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); $result_count = count($result); if ($result_count > 0) { foreach($result as $row) { $ext = $row['extension']; if(strlen($row['number_alias']) > 0) { $ext = $row['number_alias']; } $extensions[$ext]['domain_uuid'] = $row['domain_uuid']; $extensions[$ext]['extension'] = $row['extension']; $extensions[$ext]['extension_uuid'] = $row['extension_uuid']; $extensions[$ext]['number_alias'] = $row['number_alias']; $extensions[$ext]['description'] = $row['description']; } } unset ($sql, $prep_statement, $result, $row_count); // create list of extensions for query below if (isset($extensions)) { foreach ($extensions as $extension => $blah) { $ext_array[] = $extension; } $this->extensions = $extensions; } $ext_list = (isset($ext_array)) ? implode("','", $ext_array) : ""; //calculate the summary data $sql = "select "; $sql .= "caller_id_number, "; $sql .= "destination_number, "; $sql .= "billsec, "; $sql .= "hangup_cause "; $sql .= "from v_xml_cdr "; $sql .= "where "; if (!($_GET['showall'] && permission_exists('xml_cdr_all'))) { $sql .= " domain_uuid = '".$this->domain_uuid."' and "; } $sql .= "( "; $sql .= " caller_id_number in ('".$ext_list."') or "; $sql .= " destination_number in ('".$ext_list."') "; $sql .= ") "; if (!$this->include_internal) { $sql .= " and (direction = 'inbound' or direction = 'outbound') "; } if (strlen($this->start_stamp_begin) > 0 || strlen($this->start_stamp_end) > 0) { unset($this->quick_select); if (strlen($this->start_stamp_begin) > 0 && strlen($this->start_stamp_end) > 0) { $sql .= " and start_stamp between '".$this->start_stamp_begin.":00.000' and '".$this->start_stamp_end.":59.999'"; } else { if (strlen($this->start_stamp_begin) > 0) { $sql .= "and start_stamp >= '".$this->start_stamp_begin.":00.000' "; } if (strlen($this->start_stamp_end) > 0) { $sql .= "and start_stamp <= '".$this->start_stamp_end.":59.999' "; } } } else { switch ($this->quick_select) { case 1: $sql .= "and start_stamp >= '".date('Y-m-d H:i:s.000', strtotime("-1 week"))."' "; break; //last 7 days case 2: $sql .= "and start_stamp >= '".date('Y-m-d H:i:s.000', strtotime("-1 hour"))."' "; break; //last hour case 3: $sql .= "and start_stamp >= '".date('Y-m-d')." "."00:00:00.000' "; break; //today case 4: $sql .= "and start_stamp between '".date('Y-m-d',strtotime("-1 day"))." "."00:00:00.000' and '".date('Y-m-d',strtotime("-1 day"))." "."23:59:59.999' "; break; //yesterday case 5: $sql .= "and start_stamp >= '".date('Y-m-d',strtotime("this week"))." "."00:00:00.000' "; break; //this week case 6: $sql .= "and start_stamp >= '".date('Y-m-')."01 "."00:00:00.000' "; break; //this month case 7: $sql .= "and start_stamp >= '".date('Y-')."01-01 "."00:00:00.000' "; break; //this year } } $prep_statement = $this->db->prepare(check_sql($sql)); $prep_statement->execute(); $result = $prep_statement->fetchAll(PDO::FETCH_NAMED); $result_count = count($result); if ($result_count > 0) { foreach($result as $row) { if ($summary[$row['destination_number']]['missed'] == null) { $summary[$row['destination_number']]['missed'] = 0; } if (in_array($row['caller_id_number'], $ext_array)) { $summary[$row['caller_id_number']]['outbound']['count']++; $summary[$row['caller_id_number']]['outbound']['seconds'] += $row['billsec']; } if (in_array($row['destination_number'], $ext_array)) { $summary[$row['destination_number']]['inbound']['count']++; $summary[$row['destination_number']]['inbound']['seconds'] += $row['billsec']; if ($row['billsec'] == "0") { $summary[$row['destination_number']]['missed']++; } } if ($row['hangup_cause'] == "NO_ANSWER") { $summary[$row['destination_number']]['no_answer']++; } if ($row['hangup_cause'] == "USER_BUSY") { $summary[$row['destination_number']]['busy']++; } } //end foreach } //end if results unset ($sql, $prep_statement, $result, $row_count); //return the array return $summary; } } //end scripts class } /* //example use $cdr = new xml_cdr; $cdr->read_files(); */ ?>