FreePBX + Asternic CDR Reports + Recordings

FreePBX, Asternic CDR Reports и записи звонков

Эта статья на Хабре https://habr.com/ru/post/244321/

Версии ПО

FreePBX 2.11.0.41

Asternic CDR Reports 1.5.1

Введение

Классический CDR Reports, “идущий в комплекте” с FreePBX умеет делать отчёты с проигрыванием аудиозаписи, если она велась, но не умеет разграничивать доступы по Extension`ам (Extension Range, который задаётся при создании новой учётной записи админа).

Модуль Asternic CDR Reports учитывает это, но не выводит аудиофайл.

Исправим это.

Информация о звонках в Asterisk`е хранится в mysql базе данных asteriskcdrdb, таблица cdr.

<?php

$resultscdr = $dbcdr->getAll($query, DB_FETCHMODE_ASSOC);
...
foreach($resultscdr as $row)
...
    if ($row['recordingfile']) {
        $rec_parts = explode('-',$row['recordingfile']);
        $fyear = substr($rec_parts[3],0,4);
        $fmonth = substr($rec_parts[3],4,2);
        $fday = substr($rec_parts[3],6,2);
        $monitor_base = $amp_conf['MIXMON_DIR'] ? $amp_conf['MIXMON_DIR'] : $amp_conf['ASTSPOOLDIR'] . '/monitor';
        $recordingfile = "$monitor_base/$fyear/$fmonth/$fday/" . $row['recordingfile'];
        if (!file_exists($recordingfile)) {
            $recordingfile = '';
        }
    } else {
        $recordingfile = '';
    }
...

Из вышеприведённого кода файла page.cdr.php модуля обычного CDR Reports ясно, что всё, что нам нужно - это прочитать значение ячейки recordingfile (там хранится строка “файл.wav”) и добавить к нему полный путь к файлу, выдираемый, кстати, из самого же имени файла

<?php

$fyear = substr($rec_parts[3],0,4);
$fmonth = substr($rec_parts[3],4,2);
$fday = substr($rec_parts[3],6,2);
$monitor_base = $amp_conf['MIXMON_DIR'] ? $amp_conf['MIXMON_DIR'] : $amp_conf['ASTSPOOLDIR'] . '/monitor';
$recordingfile = "$monitor_base/$fyear/$fmonth/$fday/" . $row['recordingfile'];

$amp_conf - глобальный массив, из которого берутся пути к записям. Поэтому, если менялся путь по-умолчанию, всё ок.

Практика

В файле functions.inc.php модуля Asternic ищем строку

<?php

$query.= "billsec,duration,duration-billsec as ringtime,src,";

добавляем к запросу recordingfile

<?php

$query.= "billsec,duration,duration-billsec as ringtime,src,recordingfile,";

Затем вместо

<?php

$detail[$row['chan1']].= "n<td>";
 
$uni = $row['uniqueid'];
$uni = str_replace(".","",$uni);
 
if($row['userfield']<>"") {
  $detail[$row['chan1']].="<a href="javascript:void(0);" onclick='javascript:playVmail("".$row['userfield']."","play".$uni."");'>";
  $detail[$row['chan1']].="<div class='playicon' title='Play' id='play".$uni."'  style='float:left;'>";
  $detail[$row['chan1']].="<img src='images/blank.gif' alt='pixel' height='16' width='16' border='0'>";
  $detail[$row['chan1']].="</div></a>";
  $detail[$row['chan1']].="<a href="javascript:void(0); return false;" onclick='javascript:downloadVmail("".$row['userfield']."","play".$uni."","$ftype","$fdisplay","$ftab"); return false;'>";
  $detail[$row['chan1']].="<div class='downicon' title='Download' id='dload".$uni."'  style='float:left;'>";
  $detail[$row['chan1']].="<img src='images/blank.gif' alt='pixel' height='16' width='16' border='0'>";
  $detail[$row['chan1']].="</div></a>";
} else {
  $detail[$row['chan1']].= "&nbsp;";
}
  $detail[$row['chan1']].= "</td>n";

ставим

<?php

if ($row['recordingfile']) {
  $rec_parts = explode('-',$row['recordingfile']);
  $fyear = substr($rec_parts[3],0,4);
  $fmonth = substr($rec_parts[3],4,2);
  $fday = substr($rec_parts[3],6,2);
  $monitor_base = $amp_conf['MIXMON_DIR'] ? $amp_conf['MIXMON_DIR'] : $amp_conf['ASTSPOOLDIR'] . '/monitor';
  $recordingfile = "$monitor_base/$fyear/$fmonth/$fday/" . $row['recordingfile'];
  if (!file_exists($recordingfile)) {
    $recordingfile = '';
    $detail[$row['chan1']].= "n<td>";
  }
  else {
    $detail[$row['chan1']].= "n<td style='text-align: center;' title="$row[recordingfile]"><a href="".$PHP_SELF."?getRec=".base64_encode($recordingfile)."" target="_blank"><img src="images/asternic_playicon.png" alt="Call recording" /></a>";
  }
} else {
  $recordingfile = '';
  $detail[$row['chan1']].= "n<td>";
}
  $detail[$row['chan1']].= "</td>n";

По-умолчанию в ячейке Listen в Asternic выводится запись голосовой почты, но нас-то интересует запись звонка, поэтому меняем содержимое всей ячейки.

В конце файла добавляем ещё функцию, отдающую файл двоичными данными (т.е. никакой apache не имеет доступа к каталогу записи, а отдаётся всё через php) и проверку на наличие переменной getRec, в случае наличия которой получаем файл.

<?php

function recordfile_uri($path) {
  $size = filesize($path);
  $name = basename($path);
  $extension = strtolower(substr(strrchr($name,"."),1));
  // This will set the Content-Type to the appropriate setting for the file
  $ctype ='';
  switch( $extension ) {
    case "WAV":
    $ctype="audio/x-wav";
    break;
  case "wav":
    $ctype="audio/x-wav";
    break;
  case "ulaw":
    $ctype="audio/basic";
    break;
  case "alaw":
    $ctype="audio/x-alaw-basic";
    break;
  case "sln":
    $ctype="audio/x-wav";
    break;
  case "gsm":
    $ctype="audio/x-gsm";
    break;
  case "g729":
    $ctype="audio/x-g729";
    break;
  default: //not downloadable
    // echo ("<b>404 File not found! foo</b>");
    // TODO: what to do if none of the above work?
    break ;
  }
 
  $fp=fopen($path, "rb");
  if ($size && $ctype && $fp) {
    header("Pragma: public");
    header("Expires: 0");
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0");
    header("Cache-Control: public");
    header("Content-Description: audio file");
    header("Content-Type: " . $ctype);
    header("Content-Disposition: attachment; filename=" . $name);
    header("Content-Transfer-Encoding: binary");
    header("Content-length: " . $size);
    $chunksize = 1*(1024*1024);
    while (!feof($fp)) {
      $buffer = fread($fp, $chunksize);
      echo $buffer;
      ob_flush();
      flush();
    }
    fclose($fp);
  }
}
if(isset($_GET['getRec'])){
  recordfile_uri(base64_decode($_GET['getRec']));
  die();
}