image

FreePBX — это наиболее популярный web интерфейс для настройки серверов на базе Asterisk. FreePBX — это гибкая, модульная система. Предлагает богатый функционал по настройке станций. Самое приятное — это проект с открытым исходным кодом.


На практике, часто возникает необходимость решить уникальную задачу, для которой не достаточно типовых возможностей FreePBX.


В рамках статьи, я опишу возможности расширения функционала дополнительными модулями.
Опишу процесс разработки нового модуля…


Когда нужна разработка модуля?


Тиражируемый продукт — в случае, если поддерживать приходится несколько однотипных АТС и одна и та же фича необходима многим. К примеру (обратный звонок, интеграция с битрикс чатом)


Удобство настройки — загрузить модуль проще и быстрее, чем править конфиги вручную. Один раз заложили логику, и каждая последующая инсталляция требует меньше времени.


Снижаем шанс на ошибку — базовые настройки обычно производим на тестовом стенде, при переносе в продакшн могут возникнуть сложности с зависимостями. Мы можем поместить все необходимые файлы в наш модуль и установить их вместе с ним.


БОЛЬШИЕ Возможности — разработка модулей позволяет более гибко / тонко настроить АТС, к примеру возможно переопределять diaplan, или добавлять в существующий свои строчки с точностью до номера приоритета.


Схема работы Asterisk — FreePBX — DB



Asterisk может использовать базу данных для хранения истории звонков.
FreePBX — взаимодействует с базой данных, сохраняет и получает настройки. Модули FreePBX могут обращаться к базе данных для анализа истории звонков.


Одна из функций FreePBX — создание конфигурационных файлов и поставка AGI скриптов Asterisk. FreePBX знает все о системных директориях Asterisk, может управлять Asterisk.


FreePBX — модульная система



Основа FreePBX — модуль “FreePBX Framework” (далее просто Framework). По своей сути — это модуль управляющий другими модулями. Framework предоставляет базовый web интерфейс:


  • Окно авторизации
  • Главное меню
  • Страница “Module Admin”


Каждый модуль может расширить функционал FreePBX, к примеру добавить конфиги и расширить web интерфейс.
Все модули существенно зависят от Framework, и могут совсем не зависить от прочих модулей. Примеры модулей:


  • Сore — предоставляет функционал “Trunk” / “In. rout” / “Out. rout” и функционал “Application” — “Extensions”.
  • IVR — предоставляет функции голосового меню
  • Queues — функции очередей вызовов и т.д

Сохранение конфигурации FreePBX


После того, как мы что либо изменили в настройках FreePBX появляется кнопка “Apply Config”. На схеме ниже описан процесс генерации конфигурационных файлов.



Первым в дело вступает модуль Framework


  • Производит сохранение настроек в базу данных
  • Создает базовые конфиги, к примеру extensions.conf и extensions_additional.conf

Далее Framework обращается к дополнительным модулям, к примеру модуль core


  • Использует функционал модуля Framework и дополняет файл extensions_additional.conf своим dialplan
  • Создает файлы sip_additional.conf, res_odbc_additional.conf и прочие

Мы, как разработчики, можем добавить свой модуль


  • Использовать функции Framework для генерации extensions_additional.conf
  • Использовать функции прочих модулей, к примеру core для правки конфигов этого модуля: sip_additional.conf, res_odbc_additional.conf
  • Создавать свои конфигурационные файлы
  • Использовать свои AGI скрипты
  • Расширять web интерфейс своими страницами / или своими web интерфейсами

Структура модуля


module.xml
В этом файле описываются основные свойства модуля.
Наиболее важные свойства:


  • rawname” — уникальное имя модуля, не должно содержать спецсимволы
  • name” — представление модуля, как он будет отображаться в “Module admin”, не должно содержать спецсимволы
  • version” — версия модуля
  • category” — категория основного меню
  • menuitems” — представление модуля в меню, может содержать в себе теги ”menuitems” определяющие имена страниц
  • depends” — основные зависимости модуля

Структура файла детально описана в документации.
Пример файла приведен ниже:


<module>
    <rawname>pt1ctraining</rawname>
    <name>AA Training module</name>
    <version>2.11.0.6</version>
    <publisher>telefon1c.ru</publisher>
    <license>GPLv3+</license>
    <licenselink>http://www.gnu.org/licenses/gpl-3.0.txt</licenselink>
    <category>Applications</category>
    <menuitems>
        <pt1ctraining>AA Training module (MIKO LCC)</pt1ctraining>
    </menuitems>
    <changelog>
        *2.11.0.6* Первый релиз
    </changelog>
    <depends>
        <phpversion>5.3.3</phpversion>
<module>pt1c ge 2.11.3.18</module>
    </depends>
</module>

install.php


В этом файле описываем инструкции по установке модуля.
В этом скрипте возможно обратиться к “global” переменным FreePBX.


Переменная “$db” позволяет взаимодействовать с базой данных FreePBX.
Пример создания таблицы для хранения настроек модуля:


out("Начало установки модуля.");

global $db;
$sql = "CREATE TABLE IF NOT EXISTS pt1ctraining (
    id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
    pt1ctraining_id INTEGER NOT NULL,
    description VARCHAR( 150 ),
    destination VARCHAR( 50 ),
    content_app text NOT NULL,
    path_to_php_agi VARCHAR( 50 )
);";
$check = $db->query($sql);
if (DB::IsError($check)) {
    die_freepbx( "Can not create `pt1ctraining` table: " . $check->getMessage() .  "\n");
}

Обратите внимание, что нет необходимости указывать параметры подключения к базе данных.


Переменная “$amp_conf” содержит параметры конфигурации FreePBX (обычно определены в /etc/freepbx.conf и в /etc/amportal.conf). Пример использования:


global $amp_conf; 
out("AMPDBENGINET: " + $amp_conf["AMPDBENGINE"]);

Функция out() позволяет выводить информацию о ходе установки модуля в окно сообщений:



Пример добавления настройки в раздел “Settings — Advanced Settings”:



$freepbx_conf =& freepbx_conf::create();
if (!$freepbx_conf->conf_setting_exists('pt1ctraining_test')) {
    $set = array();
    $set['value']       = 'all';
    $set['defaultval']  = &$set['value'];
    $set['readonly']    = 0;
    $set['hidden']      = 0;
    $set['level']       = 3;
    $set['module']      = 'pt1ctraining';
    $set['category']    = 'AA Test Module';
    $set['emptyok']     = 0;
    $set['sortorder']   = 11;
    $set['name']        = 'Test settings.';
    $set['description'] = 'Description test settings.';
    $set['type']        = CONF_TYPE_TEXT;
    $freepbx_conf->define_conf_setting('pt1ctraining_test',$set,true);
}

Директории etc и agi-bin

При установке модуля содержимое директорий будет скопировано в соответсвующие директории asterisk:



Uninstall.php

Скрипт описывает инструкции по удалению модуля. Чистим за собой.


<?php
if (!defined('FREEPBX_IS_AUTH')) { die('No direct script access allowed'); }
sql('DROP TABLE pt1ctraining');
?>

В примере выше описан пример использования глобальной функции с именем “sql”. В качестве аргумента передаем тест запроса для удаления таблицы.


Кроме того, используется интересный вызов “defined('FREEPBX_IS_AUTH')”, проверка авторизован ли пользователь. Лучше ее использовать для всех php скриптов.


Все скрипты и файлы с именем index.php публикуются на web сервере и доступы извне.


Если пользователь попробует обратиться к скрипту без авторизации — получит уведомление “No direct script access allowed



Сердце модуля — functions.inc.php


В этом файле мы реализуем функции нашего модуля, ключевые возможности:


  • Создавать свои конфигурационные файлы
  • Добавлять варианты “destination”
  • Использовать функции других модулей
  • Править файл extensions_additional.conf и другие

В functions.inc.php могут быть определены hook-функции, которые будут вызваны модулем framework при генерации конфигов.


Такие функции обычно именуются следующим образом:


function ModuleName_FunctionName($engine) {
    //
    //
}

Пример реальной функции

Определим dialplan в extensions_additional.conf


function pt1ctraining_get_config($engine) {
    global $ext; 

    switch ($engine) {
    case 'asterisk':
        // Создаем свой контекст.
        $context = 'ext-pt1ctraining';
        $exten    = '_X!';
        $ext->add($context, $exten, '', new ext_agi('pt1ctraining_AGI.php'));
        $ext->add($context, $exten, '', new ext_hangup(''));
    }
}

Функция “get_config” будет вызвана при создании конфигурационных файлов, когда будет нажата кнопка “Apply Config”.
В качестве параметра будет передана строка “$engine” — имя движка, обычно “asterisk”.


Для создания dialplan использовалась глобальная переменная “$ext”, содержит экземпляр класса “extensions”, класс определен в файле “/var/www/html/admin/libraries/extensions.class.php” и предоставляет нам набор инструментов для генерации dialplan.


Результат работы будет добавлен в extensions_additional.conf:


[ext-pt1ctraining]
exten => _X!,1,AGI(pt1ctraining_AGI.php)
exten => _X!,n,Hangup

Классы “ext_agi” и “ext_hangup” также определены в “extensions.class.php”.


Пример добавления в секцию “[globals]” файла extensions_additional.conf

global $ext;
// Добавим инициализацию переменной в сеции "global"
$ext->addGlobal('PT1C_TR', 'test');
$ext->addGlobal("#include extension_add_pt1c.conf"."\n;", '\n');

Результат работы:


[globals]
CFDEVSTATE = TRUE
CAMPONTOGGLE = *84
DNDDEVSTATE = TRUE
FMDEVSTATE = TRUE
PT1C_TR = test
#include extension_add_pt1c.conf
; = \n
QUEDEVSTATE = TRUE

Для подключения дополнительного файла использовал хитрость в виде переноса строки — другого решения пока нет.


Используем настройку из «Advanced Settings»


Получение глобальной настройки FreePBX.


$freepbx_conf =& freepbx_conf::create();
$pt1c_events = $freepbx_conf->get_conf_setting('pt1ctraining_test',true);
// Созадем dialplan
$ext->add('ext-pt1ctraining-test', '_X!', '', new ext_noop("$pt1c_events"));
$ext->add('ext-pt1ctraining-test', '_X!', '', new ext_hangup(''));

Результат в файле extensions_additional.conf:

[ext-pt1ctraining-test]
exten => _X!,1,Noop(Privet!!!)
exten => _X!,n,Hangup

Правка res_odbc_additional.conf

Используем функционал модуля core:


global $core_conf, $amp_conf;

$section = 'PT1C_asteriskcdrdb';
$core_conf->addResOdbc($section, array('enabled' => 'yes'));
$core_conf->addResOdbc($section, array('dsn' => 'MySQL-asteriskcdrdb'));
$core_conf->addResOdbc($section, array('pooling' => 'no'));
$core_conf->addResOdbc($section, array('limit' => '1'));
$core_conf->addResOdbc($section, array('pre-connect' => 'yes'));
$core_conf->addResOdbc($section, array('username' => $amp_conf['AMPDBUSER']));
$core_conf->addResOdbc($section, array('password' => $amp_conf['AMPDBPASS']));

Результат работы в res_odbc_additional.conf:

[PT1C_asteriskcdrdb]
enabled=>yes
dsn=>MySQL-asteriskcdrdb
pooling=>no
limit=>1
pre-connect=>yes
username=>freepbxuser
password=>d52d251931c2

Создаем свой конфиг

Пожет появиться потребность создать свои конфигурационные файлы.
Для этих целей мы должны определить клас с именем “ModuleName_conf”. Пример класса приведен ниже:


// Класс будет использоваться для генерации конфигурационных файлов.
class pt1ctraining_conf {
    function get_filename() {
        $files = array(
            'extension_additional_pt1ctraining.conf',
        );
        return $files;
    }
    function generateConf($file) {
        switch ($file) {
        case 'extension_additional_pt1ctraining.conf':
            return $this->generate_conf();
            break;
        }
    }
    function generate_conf() {
        $output = "[test] ; row 1 \n; Privet";
        return $output;
    }
}

Метод “get_filename” возвращает массив файлов, которые будут созданы.
Метод “generateConf” принимает в качестве параметра имя файла и возвращает текстовое содержимое этого файла.


Определим свои destination


Каждый, кто работал с FreePBX видел поле Set Destination. Каждый модуль может добавлять свои точки назначения. Для этого необходимо определить две процедуры:


// Влияет на появление позиции в списке в поле destination.
// 
function pt1ctraining_getdest($exten) {
    return array('pt1ctraining,'.$exten.',1');
}
// После выбора "application" появится поле для выбора сохраненного маршрута.
// 
function pt1ctraining_destinations() { 
    $extens[] = array('destination' => 'ext-pt1ctraining,${EXTEN},1'  , 'description' => 'IVR');
    $extens[] = array('destination' => 'ext-pt1ctraining_2,${EXTEN},1', 'description' => 'IVR_2');
    $extens[] = array('destination' => 'ext-pt1ctraining_3,${EXTEN},1', 'description' => 'IVR_3');

    return $extens;
}

Функция “pt1ctraining_getdest” должна вернуть массив с одним значением “стока”. Формат “array('ModuleName,'.$exten.',1')


Функция “pt1ctraining_destinations” должна вернуть массив с точками назначения.
Каждый элемент массива — ассоциативный массив с двумя ключами:
'Destination' — содержит Goto совместимые параметры, в дальнейшем перенаправление будет осуществляться в dialplan средствами Goto
'Description' содержит название точки назначение, именно так будет представлено значение пользователю


Страницы модуля


В директории модуля может быть определено сколько угодно файлов с именем формата “page.ModuleName.php”, пример “page.pt1ctraining.php”.


В этих файлах мы можем определить форму html для взаимодействия с пользователем.


Чтобы web интерфейс “узнал” о существовании страницы, мы должны указать на идентификатор страницы в файле “module.xml” в теге “menuitems” тегом с именем страницы:


<menuitems>
    <pt1ctraining>AA Training module (MIKO LCC)</pt1ctraining>
</menuitems>

Пример страницы

<form autocomplete="off" name="edit" action="<?php $_SERVER['PHP_SELF'] ?>" method="post" onsubmit="return edit_onsubmit();">
    <input type="hidden" name="itemid" value="<?php echo $itemid?>">
    <input type="hidden" name="action" value="<?php echo ($itemid ? 'edit' : 'add') ?>">
    <table>
    <tr><td colspan="4"><h5>Разрабатваем модуль<hr></h5></td></tr>
    <tr>
        <td><a href="#" class="info">Наименование<span>Имя приложения</span></a></td>
        <td><input type="text"  name="description" class="" value="<?php echo($thisItem['description']); ?>"></td>
    </tr>
    <tr>
        <td><a href="#" class="info">Dialplan:<span>Text dialplan application.</span></a></td>
        <td><textarea name="content_app" cols=50 rows=5><?php echo($thisItem['content_app']); ?></textarea></td>
    </tr>
    </table>
</form>

Сборка модуля


Тут все просто, упаковка модуля должна быть произведена средствами tar:


tar -czf pt1ctraining “pt1ctraining-2.11.0.6.tgz";

Если модуль подготавливается для FreePBX 12+ то желательно подписать модуль цифровой подписью разработичка, подробности описаны в официальной документации.


Подписать модуль можно используя пакет утилит devtools:


sign.php pt1ctraining "KEY"

Где, “pt1ctraining” — каталог с модулем.


Заключение


FreePBX интересная платформа. Предлагает обширный функционал для настройки АТС.
Для случая, когда фукнционала FreePBX не достаточно есть возможность расширить набор функции средствами дополнительного модуля.


Собственный модуль позволит получить более тонкую настройку АТС:


  • Добавление своих dialplan
  • Дополнение существующих конфигов, к примеру extensions_additional.conf
  • Добавление новых конфигурационных файлов
  • Правка dialplan, созданных другими модулями

В качестве источников знаний рекомендую использовать:


Поделиться с друзьями
-->

Комментарии (3)


  1. silverjoe
    28.08.2016 11:09
    +3

    Круто! Алексей только вчера проводил мастер-класс на конференции Asterconf, а сегодня уже статья на Хабре!
    Спасибо!


    1. boffart
      29.08.2016 08:50
      +1

      Пожалуйста. Организаторам предоставил все материалы, должны будут участникам разослать.
      Дополнительно очень жду видео выступления ))


  1. sardigital
    28.08.2016 22:14
    +1

    Вчера упоминали на конференции про эту статью, а вот и она!