5 * Functions for the interface to the call monitor recordings
9 * Class for Callmonitor
14 * rank (for prioritizing modules)
29 * Adds menu item to nav menu
34 function navMenu($args) {
36 $ret .= "<p><small><small><a href='" . $_SESSION['ARI_ROOT'] . "?m=Callmonitor&f=display'>" . _("Call History") . "</a></small></small></p><br>";
42 * Acts on the selected call monitor recordings in the method indicated by the action and updates page
47 function recAction($args) {
50 $m = getArgument($args,'m');
51 $a = getArgument($args,'a');
52 $q = getArgument($args,'q');
53 $start = getArgument($args,'start');
54 $span = getArgument($args,'span');
55 $order = getArgument($args,'order');
56 $sort = getArgument($args,'sort');
57 $duration_filter = getArgument($args,'duration_filter');
61 foreach($_REQUEST as $key => $value) {
62 if (preg_match('/selected/',$key)) {
63 array_push($files, $value);
68 $this->deleteRecData($files);
75 setcookie("ari_duration_filter", $duration_filter, time()+365*24*60*60);
78 // redirect to see updated page
83 window.location = \"" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&q=" . $q . "&start=" . $start . "&span=" . $span . "&order=" . $order . "&sort=" . $sort . "&duration_filter=" . $duration_filter . "\"
97 function display($args) {
99 global $ASTERISK_CALLMONITOR_PATH;
100 global $CALLMONITOR_ALLOW_DELETE;
101 global $AJAX_PAGE_REFRESH_ENABLE;
103 $display = new DisplaySearch();
105 // get the search string
106 $m = getArgument($args,'m');
107 $f = getArgument($args,'f');
108 $q = getArgument($args,'q');
109 $start = getArgument($args,'start');
110 $span = getArgument($args,'span');
111 $order = getArgument($args,'order');
112 $sort = getArgument($args,'sort');
113 $duration_filter = getArgument($args,'duration_filter');
115 $start = $start=='' ? 0 : $start;
116 $span = $span=='' ? 15 : $span;
117 $order = $order=='' ? 'calldate' : $order;
118 $sort = $sort=='' ? 'desc' : $sort;
120 $displayname = $_SESSION['ari_user']['displayname'];
121 $extension = $_SESSION['ari_user']['extension'];
124 $record_count = $this->getCdrCount($q,$duration_filter);
125 $data = $this->getCdrData($q,$duration_filter,$start,$span,$order,$sort);
127 // get the call monitor recording files
128 $paths = split(';',$ASTERISK_CALLMONITOR_PATH);
129 foreach($paths as $key => $path) {
130 if (!is_dir($path)) {
131 $_SESSION['ari_error'] .= sprintf(_("Path is not a directory: %s"),$path) . "<br>";
134 $recordings = $this->getRecordings($ASTERISK_CALLMONITOR_PATH,$data);
137 if ($CALLMONITOR_ALLOW_DELETE) {
139 <button class='infobar' type='submit' onclick=\"document.callmonitor_form.a.value='delete'\">
146 <small>" . _("duration") . "</small>
147 <input name='duration_filter' type='text' size=4 maxlength=8 value='" . $_COOKIE['ari_duration_filter'] . "'>
148 <button class='infobar' type='submit' onclick=\"document.callmonitor_form.a.value='ignore'\">
153 if ($CALLMONITOR_ALLOW_DELETE) {
154 $recording_delete_header = "<th></th>";
157 $fields[0]['field'] = "calldate";
158 $fields[0]['text'] = _("Date");
159 $fields[1]['field'] = "calldate";
160 $fields[1]['text'] = _("Time");
161 $fields[2]['field'] = "clid";
162 $fields[2]['text'] = _("Caller ID");
163 $fields[3]['field'] = "src";
164 $fields[3]['text'] = _("Source");
165 $fields[4]['field'] = "dst";
166 $fields[4]['text'] = _("Destination");
167 $fields[5]['field'] = "dcontext";
168 $fields[5]['text'] = _("Context");
169 $fields[6]['field'] = "duration";
170 $fields[6]['text'] = _("Duration");
173 while ($fields[$i]) {
175 $field = $fields[$i]['field'];
176 $text = $fields[$i]['text'];
177 if ($order==$field) {
179 $currentSort = 'desc';
180 $arrowImg = "<img src='theme/images/arrow-asc.gif' alt='sort'>";
183 $currentSort = 'asc';
184 $arrowImg = "<img src='theme/images/arrow-desc.gif' alt='sort'>";
193 $currentSort = 'desc';
196 $unicode_q = urlencode($q);
197 $recording_header .= "<th><a href=" . $_SESSION['ARI_ROOT'] . "?m=" . $m . "&f=" . $f . "&q=" . $unicode_q . "&order=" . $field . "&sort=" . $currentSort . ">" . $text . $arrowImg . "</a></th>";
201 $recording_header .= "<th>" . _("Monitor") . "</th>";
204 foreach($data as $key=>$value) {
207 $recording = $recordings[$value['uniqueid'] . $value['calldate']];
210 $buf = split(' ', $value[calldate]);
214 // recording delete checkbox
215 if ($CALLMONITOR_ALLOW_DELETE) {
216 $recording_delete_checkbox = "<td class='checkbox'><input type=checkbox name='selected" . ++$i . "' value=" . $recording . "></td>";
220 if (is_file($recordings[$value['uniqueid'] . $value['calldate']])) {
221 $recordingLink = "<a href='#' onClick=\"javascript:popUp('misc/recording_popup.php?recording=" . $recording . "&date=" . $date . "&time=" . $time . "'); return false;\">" . _("play") . "</a>";
224 $recording_body .= "<tr>
225 " . $recording_delete_checkbox . "
226 <td width=70>" . $date . "</td>
227 <td>" . $time . "</td>
228 <td>" . $value[clid] . "</td>
229 <td>" . $value[src] . "</td>
230 <td>" . $value[dst] . "</td>
231 <td>" . $value[dcontext] . "</td>
232 <td width=90>" . $value[duration] . " sec</td>
233 <td>" . $recordingLink . "</td>
237 $recording_body .= "<tr></tr>";
242 $url_opts['sort'] = $sort;
243 $url_opts['order'] = $order;
244 $url_opts['duration_filter'] = $duration_filter;
246 // build page content
247 $ret .= checkErrorMessage();
249 // ajax page refresh script
250 if ($AJAX_PAGE_REFRESH_ENABLE) {
251 // $ret .= ajaxRefreshScript($args);
255 if ($_SESSION['ari_user']['admin_callmonitor']) {
256 $header_text = _("Call History");
258 $header_text = sprintf(_("Call History for %s (%s)"),$displayname,$extension);
260 $ret .= $display->displayHeaderText($header_text);
261 $ret .= $display->displaySearchBlock('left',$m,$q,$url_opts,true);
264 if ($CALLMONITOR_ALLOW_DELETE) {
267 <form name='callmonitor_form' action='" . $_SESSION['ARI_ROOT'] . "' method='GET'>
268 <input type=hidden name=m value=" . $m . ">
269 <input type=hidden name=f value=recAction>
270 <input type=hidden name=a value=''>
271 <input type=hidden name=q value=" . $q . ">
272 <input type=hidden name=start value=" . $start . ">
273 <input type=hidden name=span value=" . $span . ">
274 <input type=hidden name=order value=" . $order . ">
275 <input type=hidden name=sort value=" . $sort . ">";
278 $ret .= $display->displayInfoBarBlock($controls,$q,$start,$span,$record_count);
280 // javascript for popup and message actions
282 <SCRIPT LANGUAGE='JavaScript'>
284 function popUp(URL) {
285 eval(\"page = window.open(URL, 'play', 'toolbar=0,scrollbars=0,location=0,statusbar=0,menubar=0,resizable=1,width=324,height=110');\");
288 function checkAll(form,set) {
291 while (elem = form.elements[i]) {
295 elem.checked = false;
304 // call monitor delete recording controls
305 if ($CALLMONITOR_ALLOW_DELETE) {
310 <small>" . _("select") . ": </small>
311 <small><a href='' OnClick=\"checkAll(document.callmonitor_form,true); return false;\">" . _("all") . "</a></small>
312 <small><a href='' OnClick=\"checkAll(document.callmonitor_form,false); return false;\">" . _("none") . "</a></small>
323 <table class='callmonitor'>
325 " . $recording_delete_header . "
326 " . $recording_header . "
328 " . $recording_body . "
331 $start = getArgument($args,'start');
332 $span = getArgument($args,'span');
333 $order = getArgument($args,'order');
334 $sort = getArgument($args,'sort');
337 if ($CALLMONITOR_ALLOW_DELETE) {
341 $ret .= $display->displaySearchBlock('center',$m,$q,$url_opts,false);
342 $ret .= $display->displayNavigationBlock($m,$q,$url_opts,$start,$span,$record_count);
348 * Checks for a recording file
350 * @param $asterisk_callmonitor_path
351 * path call monitor recording directory on the asterisk server
353 * current call monitor recordings on the asterisk server
355 * returns an array of $recording file names if found
357 function getRecordings($asterisk_callmonitor_path,$data) {
359 global $CALLMONITOR_ONLY_EXACT_MATCHING;
360 global $CALLMONITOR_AGGRESSIVE_MATCHING;
362 $recordings = array();
364 $extension = $_SESSION['ari_user']['extension'];
366 $paths = split(';',$asterisk_callmonitor_path);
367 foreach($paths as $key => $path) {
368 $paths[$key] = fixPathSlash($paths[$key]);
372 if (!$CALLMONITOR_ONLY_EXACT_MATCHING) {
376 foreach($paths as $key => $path) {
377 $path_files = getFiles($path,$filter,$recursiveMax,$recursiveCount);
379 $files = array_merge($files,$path_files);
385 foreach($data as $data_key => $data_value) {
389 $calldate = $data_value['calldate'];
390 $duration = $data_value['duration'];
391 $lastdata = $data_value['lastdata'];
392 $uniqueid = $data_value['uniqueid'];
393 $userfield = $data_value['userfield'];
396 $st = trim(strtotime($calldate));
397 $et = trim(strtotime($calldate) + $duration); // for on-demand call recordings
401 $buf = preg_replace('/\-|\:/', '', $calldate);
402 $calldate_key = preg_replace('/\s+/', '-', $buf);
403 $unique_file_key = $calldate_key . "-" . $uniqueid;
405 if ($unique_file_key=='') {
406 $buf = preg_split("/\|/", $lastdata);
407 $unique_file_key = $buf[1];
411 foreach($paths as $callmonitor_key => $path) {
413 // try to find an exact match using the uniqueid
414 if (isset($uniqueid)) {
416 $check_files = array();
417 array_push($check_files,$path . $uniqueid . ".WAV");
418 array_push($check_files,$path . $uniqueid . ".wav");
419 array_push($check_files,$path . $uniqueid . ".gsm");
421 array_push($check_files,$path . $unique_file_key . ".WAV");
422 array_push($check_files,$path . $unique_file_key . ".wav");
423 array_push($check_files,$path . $unique_file_key . ".gsm");
425 array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".WAV");
426 array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".wav");
427 array_push($check_files,$path . "g" . $extension . "-" . $unique_file_key . ".gsm");
429 array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".WAV");
430 array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".wav");
431 array_push($check_files,$path . "q" . $extension . "-" . $unique_file_key . ".gsm");
433 array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".WAV");
434 array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".wav");
435 array_push($check_files,$path . "OUT" . $extension . "-" . $unique_file_key . ".gsm");
437 array_push($check_files,$path . $userfield);
440 foreach($check_files as $check_file) {
441 if (is_file($check_file)) {
442 $recording = $check_file;
448 // if found do not need to check the rest of the paths
449 if ($recording!='') {
454 // get all the callmonitor recordings on server and try to find a non-exact match for this log entry
455 if (!$CALLMONITOR_ONLY_EXACT_MATCHING) {
457 // try to find a file using the uniqueid
460 // try and match the unique id
462 foreach($files as $key => $path) {
463 if (strlen($uniqueid)>1 && strpos($path,$uniqueid)!==FALSE) {
465 $files[$key] = ''; // remove it from the recording files so it will not be matched twice
472 // try and match a file using the calldate (if no unique number from database)
475 foreach($files as $key => $path) {
476 $parts = split("-", $path);
478 (strpos($path,$st)!==FALSE) ||
479 (strpos($path,"auto")!==FALSE && $parts[1] >= $st && $parts[1] <= $et)) {
481 $files[$key] = ''; // remove it from the recording files so it will not be matched twice
487 if ($CALLMONITOR_AGGRESSIVE_MATCHING) {
489 // one last stab at finding a recording by adding one or two seconds to the call time
495 foreach($files as $key => $path) {
496 $split = explode("-", $path);
498 && ((strpos($path,$st_1)!==FALSE) ||
499 (strpos($path,$st_2)!==FALSE) ||
500 (strpos($path,"auto")!==FALSE && $parts[1] >= $st_1 && $parts[1] <= $et_1) ||
501 (strpos($path,"auto")!==FALSE && $parts[1] >= $st_2 && $parts[1] <= $et_2))) {
503 $files[$key] = ''; // remove it from the recording files so it will not be matched twice
511 // add to array to be returned
513 $recordings[$uniqueid . $calldate] = $recording;
521 * Deletes selected call monitor recordings
524 * Array of files to delete
526 function deleteRecData($files) {
528 foreach($files as $key => $file) {
529 if (is_writable($file)) {
532 $_SESSION['ari_error'] = _("Only deletes recording files, not cdr log");
538 * Gets cdr record count
543 function getSearchText($q,$duration_filter) {
546 if ($q!='*' && $q!=NULL) {
547 $searchText .= "WHERE ";
548 $tok = strtok($q," \n\t");
550 $searchText .= " (calldate regexp '" . $tok . "'
551 OR clid regexp '" . $tok . "'
552 OR src regexp '" . $tok . "'
553 OR dst regexp '" . $tok . "'
554 OR dstchannel regexp '" . $tok . "'
555 OR dcontext regexp '" . $tok . "'
556 OR duration regexp '" . $tok . "'
557 OR disposition regexp '" . $tok . "'
558 OR uniqueid regexp '" . $tok . "'
559 OR userfield regexp '" . $tok . "'
561 $tok = strtok(" \n\t");
563 $searchText .= " AND";
569 if ($duration_filter) {
571 $searchText .= "WHERE ";
573 $searchText .= "AND ";
575 $searchText .= "duration>" . $duration_filter . " ";
579 if (!$_SESSION['ari_user']['admin_callmonitor']) {
581 $searchText .= "WHERE ";
583 $searchText .= "AND ";
586 // allow entries to be viewed with users extension
587 $searchText .= "(src = '" . $_SESSION['ari_user']['extension'] . "'
588 OR dst = '" . $_SESSION['ari_user']['extension'] . "'
590 OR channel LIKE 'IAX2/" . $_SESSION['ari_user']['extension'] ."-%'
591 OR dstchannel LIKE 'IAX2/" . $_SESSION['ari_user']['extension'] ."-%'
593 OR channel LIKE 'SIP/" . $_SESSION['ari_user']['extension'] ."-%'
594 OR dstchannel LIKE 'SIP/" . $_SESSION['ari_user']['extension'] ."-%')";
596 // allow entries to be viewed with users outbound CID
597 if (isset($_SESSION['ari_user']['outboundCID']) && trim($_SESSION['ari_user']['outboundCID']) != '') {
598 $searchText .= "OR (src = '" . $_SESSION['ari_user']['outboundCID'] . "'
599 OR dst = '" . $_SESSION['ari_user']['outboundCID'] . "')";
607 * Gets cdr record count
612 * Number of cdr records counted
614 function getCdrCount($q,$duration_filter) {
616 global $ASTERISKCDR_DBTABLE;
618 $searchText = $this->getSearchText($q,$duration_filter);
620 $dbh = $_SESSION['dbh_cdr'];
621 $sql = "SELECT count(*)
622 FROM " . $ASTERISKCDR_DBTABLE . "
625 $result = $dbh->getAll($sql);
626 if (DB::isError($result)) {
627 $_SESSION['ari_error'] = $result->getMessage();
630 $count = $result[0][0];
643 * number of records to return
645 * cdr data to be returned
647 function getCdrData($q,$duration_filter,$start,$span,$order,$sort) {
649 global $ASTERISKCDR_DBTABLE;
653 $searchText = $this->getSearchText($q,$duration_filter);
655 $dbh = $_SESSION['dbh_cdr'];
657 FROM " . $ASTERISKCDR_DBTABLE . "
659 ORDER BY " . $order . " " . $sort . "
660 LIMIT " . $start . "," . $span;
661 $result = $dbh->getAll($sql,DB_FETCHMODE_ASSOC);
662 if (DB::isError($result)) {
663 $_SESSION['ari_error'] = $result->getMessage();