Доработка выборки записей по временному диапазону в модуле UCP FreePBX
В этой статье добавим возможность фильтра по временному диапазону в модуле User Control Panel (сокращённо UCP).
Данная доработка применима для FreePBX 13 версии и модуля UCP версии 13.0.42.2. Функционирование данной доработки на других версиях модуля UCP не тестировалось. Для других версий FreePBX потребуется доработка.

Постановка задачи
Откроем FreePBX и перейдём в модуль UCP. Подробнее о том, как работать с UCP можно прочесть в статье: Настройка и использование модуля UCP (User Control Panel) во FreePBX 13
Перейдём на интересующую нас вкладку истории звонков для текущего номера — Call History. В данный момент она будет выглядеть, как представлено на скриншоте ниже:
Задача: организовать фильтр по временному диапазону из поля Date. Конечный результат представлен на скриншоте:
Как видим, появились поля, для заполнения даты и времени. Единственный нюанс — необходимо, после изменения значений данных полей, перезагрузить таблицу. Это можно сделать специальной reload-кнопкой, расположенной вверху таблицы. Эта кнопка является частью управляющей конструкции таблицы, генерируемой при помощи Bootstrap.
Пример работы изменённых скриптов представим в конце статьи, а пока перейдём к написанию кода.
Реализация
В начале реализуем часть, связанную с отображением элементов поиска на форме. Для этого открываем для редактирования файл …/admin/modules/cdr/ucp/views/view.php. В этот файл вставим формы для выбора дат. Сделаем это до открытия таблицы, то есть до открывающегося тэга <table>:
<label>С </label>
<input type=»date» id=»day_start» value=»<?php echo date(‘Y-m-01’); ?>» min=»2000-01-01″/>
<input type=»time» id=»time_start» value=»00:00″/>
<label>По </label>
<input type=»date» id=»day_end» value=»<?php echo date(‘Y-m-d’); ?>»/>
<input type=»time» id=»time_end» value=»23:59″/>
Можно использовать свой стиль форматирования здесь, так как это пример реализации работы и основной упор здесь делается на саму работу, то есть на, так называемый, back end.
Теперь коротко рассмотрим, что означает весь этот написанный код. Выбираем стандартные типы для input’ов — date и time. Обязательно указываем id для каждого элемента — впоследствии, будем осуществлять выбор значений именно по нему. В качестве значения по умолчанию для времени берём: старт в 00:00 и окончание в 23:59, для удобства выборки, чтобы сразу захватывался весь день. Со значением по умолчанию для дат несколько сложнее. В качестве стартовой даты берём первый день текущего месяца. А в качестве даты окончания поиска — текущий день. Код для этого модем увидеть в приведённом скрипте.
Первый результат можем уже увидеть, если обновим страницу модуля UCP на вкладке Call History.
Теперь перейдём к созданию кода функционала выборки.
На данном этапе стоит задача, чтобы при обновлении таблицы считались наши введённые данные в формы дат и их можно было бы передать для дальнейшей обработки. Так как вся таблица построена с использованием Bootstrap, то будем редактировать обращение к серверу, написанное на javascript в Bootstrap.
Открываем файл …/admin/modules/ucp/htdocs/assets/js/bootstrap-table-1.9.0.js.
В нём находим функцию, отвечающую за инициализацию всех параметров, перед отправкой запроса на сервер. Это функция BootstrapTable.prototype.initServer. При объявлении происходит инициализация params. В неё сразу помещаются такие значения, как строка поиска, тип сортировки, номер текущей страницы и т.д.
Для получения текущих значений из форм, в которых указаны дата и время, воспользуемся функцией js: getElementById. Данная функция вернёт NULL в случае, если такой элемент не будет найден на текущей странице. Так как к этой странице идёт обращение ещё и со страницы Call Event Logs (на которой мы не устанавливали дополнительных форм времени), воспользуемся тернарным оператором для проверки существования элементов.
Сюда же поместим обработку своих переменных. Params примет вид:
params = {
pageSize: this.options.pageSize === this.options.formatAllRows() ?
this.options.totalRows : this.options.pageSize,
pageNumber: this.options.pageNumber,
searchText: this.searchText,
sortName: this.options.sortName,
sortOrder: this.options.sortOrder,
startDate: (document.getElementById(‘day_start’) != null && document.getElementById(‘time_start’) != null) ? document.getElementById(‘day_start’).value + ‘ ‘ + document.getElementById(‘time_start’).value + ‘:00’ : «»,
endDate: (document.getElementById(‘day_end’) != null && document.getElementById(‘time_end’) != null) ? document.getElementById(‘day_end’).value + ‘ ‘ + document.getElementById(‘time_end’).value + ‘:00’ : «»
},
request;
Для проверки того, что к этому шагу всё отрабатывает, можно воспользоваться выводом текущих значений startDate и endDate.
alert(«startDate = «+params.startDate+»n endDate = «+params.endDate);
Если значения переменных равны тем, которые были выбраны в полях дат, то всё хорошо и можно двигаться дальше. Если данные по каким-то причинам не совпадают, то следует вернуться назад и просмотреть, всё ли сделано верно.
После того, как тест произведён, alert можно удалять.
Перейдём к отправке полученных данных в php скрипт. Для начала необходимо добавить к формируемым данным, полученные. Делается это в текущем скрипте bootstrap-table-1.9.0.js.
Модифицировать будем следующий фрагмент: if (this.options.queryParamsType === ‘limit’). В нём находится новый params, который будет передаваться в ajax-запросе. Добавив к нему полученные переменные, получим:
params = {
search: params.searchText,
sort: params.sortName,
order: params.sortOrder,
stdate: params.startDate,
enddate:params.endDate
};
Как видим, добавили, на основании предыдущих переменных, новые stdate и enddate.
На этом работа с файлом bootstrap-table-1.9.0.js завершена. Можем приступать к модификации файлов на php. И первый на очереди — один из классов Cdr, предназначенный для UCP. Располагается по адресу: …/admin/modules/cdr/ucp/Cdr.class.php.
Находим в нём функцию ajaxHandler(). Она отвечает за обработку ajax-запросов. Данная функция есть практически в каждом модуле FreePBX. Нас будет интересовать подраздел switch($_REQUEST[‘command’]). Это специальный подраздел переданных команд. Для построения таблицы в модуле UCP на странице Call History используется команда grid. Таким образом, находим следующий участок кода:
switch($_REQUEST[‘command’]) {
case «grid»:
Здесь производим инициализацию новых переменных, полученных из запроса:
$start_date = $_REQUEST[‘stdate’];
$end_date = $_REQUEST[‘enddate’];
Для проверки, что значения, после нашего запроса передаются верно, можно воспользоваться записью в файл.
Необходимо писать именно в файл лога, потому что использование «echo» будет ломать структуру отображения страницы.
Запись в файл производим следующим образом:
$fp = fopen(“ucp_log”,”w”);
fwrite($fp, “start_date = $start_datenend_date = $end_date”);
fclose($fp);
Сохраняем Cdr.class.php и перезагружаем страницу UCP -> Call History. Созданный файл будет находится по адресу: …/admin/modules/ucp/htdocs/. В случае успешной передачи значений через ajax-запрос, в файл будут выведены текущие выбранные дата и время. Если это верно, то данное логирование можно удалить/закомментировать.
Находим вызов функции getPages. Данная функция принадлежит классу CDR и отвечает за получение количества страниц для отображения, с текущими параметрами поиска. Если здесь не модифицировать запрос, то будет неправильно считаться количество страниц при использовании ограничения по времени. Передаём в вызов функции полученные параметры:
$pages = $this->cdr->getPages($ext,$search,$limit,$start_date,$end_date);
Находим чуть ниже вызов функции postProcessCalls, внутри которой производится вызов функции getCalls. Сама функция описана в другом файле и будет рассмотрена позднее. Добавим полученные переменные в данную функцию:
$data = $this->postProcessCalls($this->cdr->getCalls($ext,$page,$orderby,$order,$search,$limit,$start_date,$end_date),$ext);
На этом работа с данным файлом завершена.
Переходим к редактированию другого класса: …/admin/modules/cdr/Cdr.class.php. Именно в нём описывается функция getCalls. Найдём её и выполним получение ранее переданных параметров. Функция примет вид:
public function
getCalls($extension,$page=1,$orderby=’date’,$order=’desc’,$search=»,$limit=100,$start_date=»»,$end_date=»») {
По умолчанию, переменные $start_date и $end_date делаем пустыми, чтобы они не влияли на работу других модулей (если getCalls будет вызываться из других мест).
Формируем переменную для вставки в запрос:
$sqldate = ($start_date != «» && $end_date != «») ? «calldate BETWEEN ‘$start_date’ AND ‘$end_date’ AND» : «»;
Используем тернарный оператор, чтобы, в случае передачи пустых переменных, в строку запроса также добавлялась пустая строка.
Переходим к формированию самого запроса. Самих запросов будет два: если строка поиска пуста и не пуста. В оба запроса добавляем переменную $sqldate в раздел WHERE. Получим следующий фрагмент:
if(!empty($search)) {
$sql = «SELECT *, UNIX_TIMESTAMP(calldate) As timestamp FROM «.$this->db_table.» WHERE $sqldate (dstchannel LIKE :chan OR channel LIKE :chan OR src = :extension OR dst = :extension OR src = :extensionv OR dst = :extensionv OR cnum = :extension OR cnum = :extensionv) AND (clid LIKE :search OR src LIKE :search OR dst LIKE :search) ORDER by $orderby $order LIMIT $start,$end»;
$sth = $this->cdrdb->prepare($sql);
$sth->execute(array(‘:chan’ => ‘%/’.$extension.’-%’, ‘:extension’ => $extension, ‘:search’ => ‘%’.$search.’%’, ‘:extensionv’ => ‘vmu’.$extension));
} else {
$sql = «SELECT *, UNIX_TIMESTAMP(calldate) As timestamp FROM «.$this->db_table.» WHERE $sqldate (dstchannel LIKE :chan OR channel LIKE :chan OR src = :extension OR dst = :extension OR src = :extensionv OR dst = :extensionv OR cnum = :extension OR cnum = :extensionv) ORDER by $orderby $order LIMIT $start,$end»;
Теперь остаётся только поправить в этом же файле функцию getPages. Вызов будет изменён следующим образом:
public function getPages($extension,$search=»,$limit=100,$start_date=»»,$end_date=»») {
Видим, что, как и в предыдущей функции, добавились два параметра — start_date и end_date, которые пусты по умолчанию.
Формируем переменную для вставки в запрос:
$sqldate = ($start_date != «» && $end_date != «») ? «calldate BETWEEN ‘$start_date’ AND ‘$end_date’ AND» : «»;
Она аналогична переменной в функции getCalls.
И формируем сам запрос:
if(!empty($search)) {
$sql = «SELECT count(*) as count FROM «.$this->db_table.» WHERE $sqldate (src = :extension OR dst = :extension OR src = :extensionv OR dst = :extensionv OR cnum = :extension) AND (clid LIKE :search OR src LIKE :search OR dst LIKE :search)»;
$sth = $this->cdrdb->prepare($sql);
$sth->execute(array(‘:extension’ => $extension, ‘:search’ => ‘%’.$search.’%’,’:extensionv’ => ‘vmu’.$extension));
} else {
$sql = «SELECT count(*) as count FROM «.$this->db_table.» WHERE $sqldate (src = :extension OR dst = :extension OR src = :extensionv OR dst = :extensionv OR cnum = :extension)»;
$sth = $this->cdrdb->prepare($sql);
$sth->execute(array(‘:extension’ => $extension,’:extensionv’ => ‘vmu’.$extension));
}
Сохраняем файл, перезагружаем страницу UCP -> Call History. Если всё выполнено верно, то таблица будет отфильтрована по указанным датам.
Чтобы применить фильтрацию при изменении дат, необходимо нажать на кнопку обновления страницы, для отправки данных ajax-запросом.
Подробнее на скриншоте ниже:
На этом данная доработка по модулю UCP завершена.
В конце хотелось бы продемонстрировать пример работы:
Также могут быть интересны следующие статьи:
Обзор модуля User Control Panel во FreePBX 14
Создание внутренних номеров на IP АТС Asterisk

Остались вопросы?
Я - Першин Артём, менеджер компании Voxlink. Хотите уточнить детали или готовы оставить заявку? Укажите номер телефона, я перезвоню в течение 3-х секунд.
категории
- DECT
- Linux
- Вспомогательный софт при работе с Asterisk
- Интеграция с CRM и другими системами
- Интеграция с другими АТС
- Использование Elastix
- Использование FreePBX
- Книга
- Мониторинг и траблшутинг
- Настройка Asterisk
- Настройка IP-телефонов
- Настройка VoIP-оборудования
- Новости и Статьи
- Подключение операторов связи
- Разработка под Asterisk
- Установка Asterisk
VoIP оборудование
ближайшие курсы
Новые статьи
10 доводов в пользу Asterisk
Распространяется бесплатно.
Asterisk – программное обеспечение с открытым исходным кодом, распространяется по лицензии GPL. Следовательно, установив один раз Asterisk вам не придется дополнительно платить за новых абонентов, подключение новых транков, расширение функционала и прочие лицензии. Это приближает стоимость владения станцией к нулю.
Безопасен в использовании.
Любое программное обеспечение может стать объектом интереса злоумышленников, в том числе телефонная станция. Однако, сам Asterisk, а также операционная система, на которой он работает, дают множество инструментов защиты от любых атак. При грамотной настройке безопасности у злоумышленников нет никаких шансов попасть на станцию.
Надежен в эксплуатации.
Время работы серверов некоторых наших клиентов исчисляется годами. Это значит, что Asterisk работает несколько лет, ему не требуются никакие перезагрузки или принудительные отключения. А еще это говорит о том, что в районе отличная ситуация с электроэнергией, но это уже не заслуга Asterisk.
Гибкий в настройке.
Зачастую возможности Asterisk ограничивает только фантазия пользователя. Ни один конструктор шаблонов не сравнится с Asterisk по гибкости настройки. Это позволяет решать с помощью Asterisk любые бизнес задачи, даже те, в которых выбор в его пользу не кажется изначально очевидным.
Имеет огромный функционал.
Во многом именно Asterisk показал какой должна быть современная телефонная станция. За многие годы развития функциональность Asterisk расширилась, а все основные возможности по-прежнему доступны бесплатно сразу после установки.
Интегрируется с любыми системами.
То, что Asterisk не умеет сам, он позволяет реализовать за счет интеграции. Это могут быть интеграции с коммерческими телефонными станциями, CRM, ERP системами, биллингом, сервисами колл-трекинга, колл-бэка и модулями статистики и аналитики.
Позволяет телефонизировать офис за считанные часы.
В нашей практике были проекты, реализованные за один рабочий день. Это значит, что утром к нам обращался клиент, а уже через несколько часов он пользовался новой IP-АТС. Безусловно, такая скорость редкость, ведь АТС – инструмент зарабатывания денег для многих компаний и спешка во внедрении не уместна. Но в случае острой необходимости Asterisk готов к быстрому старту.
Отличная масштабируемость.
Очень утомительно постоянно возвращаться к одному и тому же вопросу. Такое часто бывает в случае некачественного исполнения работ или выбора заведомо неподходящего бизнес-решения. С Asterisk точно не будет такой проблемы! Телефонная станция, построенная на Asterisk может быть масштабируема до немыслимых размеров. Главное – правильно подобрать оборудование.
Повышает управляемость бизнеса.
Asterisk дает не просто набор полезных функций, он повышает управляемость организации, качества и комфортности управления, а также увеличивает прозрачность бизнеса для руководства. Достичь этого можно, например, за счет автоматизации отчетов, подключения бота в Telegram, санкционированного доступа к станции из любой точки мира.
Снижает расходы на связь.
Связь между внутренними абонентами IP-АТС бесплатна всегда, независимо от их географического расположения. Также к Asterisk можно подключить любых операторов телефонии, в том числе GSM сим-карты и настроить маршрутизацию вызовов по наиболее выгодному тарифу. Всё это позволяет экономить с первых минут пользования станцией.
нужная статья всегда попадается после того как сам всё сделал…
делал выбор периода с помощью daterangepicker — очень удобно оказалось.