diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
new file mode 100644
index 0000000..b7bb910
--- /dev/null
+++ b/.github/workflows/build.yml
@@ -0,0 +1,15 @@
+name: Build and Publish
+
+on:
+ push:
+ branches:
+ - master
+ - develop
+ workflow_dispatch:
+
+jobs:
+ build:
+ uses: mikopbx/.github-workflows/.github/workflows/extension-publish.yml@master
+ with:
+ initial_version: "1.7"
+ secrets: inherit
\ No newline at end of file
diff --git a/App/Controllers/ModuleConnectorFMCController.php b/App/Controllers/ModuleConnectorFMCController.php
new file mode 100644
index 0000000..9986f5a
--- /dev/null
+++ b/App/Controllers/ModuleConnectorFMCController.php
@@ -0,0 +1,107 @@
+moduleDir = PbxExtensionUtils::getModuleDir($this->moduleUniqueID);
+ $this->view->logoImagePath = "{$this->url->get()}assets/img/cache/{$this->moduleUniqueID}/logo.svg";
+ $this->view->submitMode = null;
+ parent::initialize();
+ }
+ /**
+ * Index page controller
+ */
+ public function indexAction(): void
+ {
+ $footerCollection = $this->assets->collection('footerJS');
+ $footerCollection->addJs('js/pbx/main/form.js', true);
+ $footerCollection->addJs('js/vendor/datatable/dataTables.semanticui.js', true);
+ $footerCollection->addJs("js/cache/{$this->moduleUniqueID}/module-connectorfmc-index.js", true);
+ $footerCollection->addJs('js/vendor/jquery.tablednd.min.js', true);
+ $headerCollectionCSS = $this->assets->collection('headerCSS');
+ $headerCollectionCSS->addCss("css/cache/{$this->moduleUniqueID}/module-connectorfmc.css", true);
+ $headerCollectionCSS->addCss('css/vendor/datatable/dataTables.semanticui.min.css', true);
+
+ $settings = ModuleConnectorFMC::findFirst();
+ if ($settings === null) {
+ $settings = new ModuleConnectorFMC();
+ }
+ $options = TrunksFMC::findFirst();
+ if ($options === null) {
+ $options = new TrunksFMC();
+ }
+
+ $filterSip = [
+ "type = '".Extensions::TYPE_SIP."'",
+ 'columns' => 'concat(callerid, " <", number, ">") as name, number as id',
+ ];
+ $peers = Extensions::find($filterSip);
+
+ $options = $options->toArray();
+ $options['peers'] = $peers;
+ $this->view->form = new ModuleConnectorFMCForm($settings, $options);
+ $this->view->pick("$this->moduleDir/App/Views/index");
+ }
+
+ /**
+ * Save settings AJAX action
+ */
+ public function saveAction() :void
+ {
+ $data = $this->request->getPost();
+ $settings = ModuleConnectorFMC::findFirst();
+ if ($settings === null) {
+ $settings = new ModuleConnectorFMC();
+ }
+ $trunk = TrunksFMC::findFirst();
+ if ($trunk === null) {
+ $trunk = new TrunksFMC();
+ $trunk->outputEndpoint = Sip::generateUniqueID('SIP-FMC-');
+ $trunk->outputEndpointSecret = md5($trunk->outputEndpoint.time());
+ }
+ $this->db->begin();
+ foreach ($settings as $key => $value) {
+ if(isset($data[$key])){
+ $settings->$key = trim($data[$key]);
+ }
+ }
+ foreach ($trunk as $key => $value) {
+ if(isset($data[$key])){
+ if($key === 'useDelayedResponse'){
+ $trunk->$key = ($data[$key] === 'on') ? '1' : '0';
+ }else{
+ $trunk->$key = trim($data[$key]);
+ }
+ }
+ }
+ if ($settings->save() === FALSE || $trunk->save() === FALSE) {
+ $this->view->success = false;
+ $this->db->rollback();
+ return;
+ }
+ $this->flash->success($this->translation->_('ms_SuccessfulSaved'));
+ $this->view->success = true;
+ $this->db->commit();
+ }
+}
\ No newline at end of file
diff --git a/App/Forms/ModuleConnectorFMCForm.php b/App/Forms/ModuleConnectorFMCForm.php
new file mode 100644
index 0000000..e5af7d2
--- /dev/null
+++ b/App/Forms/ModuleConnectorFMCForm.php
@@ -0,0 +1,109 @@
+.
+ */
+
+namespace Modules\ModuleConnectorFMC\App\Forms;
+
+use Modules\ModuleConnectorFMC\Models\TrunksFMC;
+use Phalcon\Forms\Form;
+use Phalcon\Forms\Element\Text;
+use Phalcon\Forms\Element\Password;
+use Phalcon\Forms\Element\Check;
+use Phalcon\Forms\Element\Hidden;
+use Phalcon\Forms\Element\Select;
+
+class ModuleConnectorFMCForm extends Form
+{
+ public function initialize($entity = null, $options = null): void
+ {
+ $this->add(new Hidden('id', ['value' => $entity->id]));
+ $this->add(new Text('userAgent'));
+ $this->add(new Text('sipPort'));
+ $this->add(new Text('rtpPortStart'));
+ $this->add(new Text('rtpPortEnd'));
+ $this->add(new Text('amiPort'));
+
+ $arrLibraryType = [
+ TrunksFMC::PROVIDER_TYPE_B24 => $this->translation->_('module_connectorfmc_PROVIDER_TYPE_B24'),
+ TrunksFMC::PROVIDER_TYPE_MCN => $this->translation->_('module_connectorfmc_PROVIDER_TYPE_MCN'),
+ ];
+
+ foreach ($options as $key => $value) {
+ if ($key === 'id') {
+ continue;
+ }
+ if ($key === 'extensions') {
+ $this->add(new Hidden($key, ['value' => $value]));
+ } elseif ($key === 'providerType') {
+ $providerType = new Select(
+ 'providerType',
+ $arrLibraryType,
+ [
+ 'using' => [
+ 'id',
+ 'name',
+ ],
+ 'useEmpty' => false,
+ 'value' => $value,
+ 'class' => 'ui selection dropdown',
+ ]
+ );
+ $this->add($providerType);
+ } elseif ($key === 'useDelayedResponse') {
+ $this->addCheckBox($key, intval($value) === 1);
+ } elseif ($key === 'incomingEndpointSecret') {
+ $this->add(new Password($key, ['value' => $value]));
+ } elseif (in_array($key, ['outputEndpoint','outputEndpointSecret'], true)) {
+ $this->add(new Text($key, ['value' => $value, 'readonly' => '']));
+ } elseif ('peers' === $key) {
+ $peers = new Select('peers', $value, [
+ 'using' => [
+ 'id',
+ 'name',
+ ],
+ 'value' => explode(',', $options['extensions']),
+ 'useEmpty' => false,
+ 'multiple' => '',
+ 'class' => 'ui fluid search dropdown',
+ ]);
+ $this->add($peers);
+ } else {
+ $this->add(new Text($key, ['value' => $value]));
+ }
+ }
+ }
+
+ /**
+ * Adds a checkbox to the form field with the given name.
+ * Can be deleted if the module depends on MikoPBX later than 2024.3.0
+ *
+ * @param string $fieldName The name of the form field.
+ * @param bool $checked Indicates whether the checkbox is checked by default.
+ * @param string $checkedValue The value assigned to the checkbox when it is checked.
+ * @return void
+ */
+ public function addCheckBox(string $fieldName, bool $checked, string $checkedValue = 'on'): void
+ {
+ $checkAr = ['value' => null];
+ if ($checked) {
+ $checkAr = ['checked' => $checkedValue,'value' => $checkedValue];
+ }
+ $this->add(new Check($fieldName, $checkAr));
+ }
+}
diff --git a/App/Views/index.volt b/App/Views/index.volt
new file mode 100644
index 0000000..de541b9
--- /dev/null
+++ b/App/Views/index.volt
@@ -0,0 +1,101 @@
+
+
\ No newline at end of file
diff --git a/Calls/agi-bin/log b/Calls/agi-bin/log
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/acl.conf b/Calls/asterisk/acl.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/ccss.conf b/Calls/asterisk/ccss.conf
new file mode 100644
index 0000000..5ea4a6e
--- /dev/null
+++ b/Calls/asterisk/ccss.conf
@@ -0,0 +1,2 @@
+[general]
+cc_max_requests = 20
diff --git a/Calls/asterisk/cdr.conf b/Calls/asterisk/cdr.conf
new file mode 100644
index 0000000..159df5c
--- /dev/null
+++ b/Calls/asterisk/cdr.conf
@@ -0,0 +1,8 @@
+[general]
+enable=no
+unanswered=yes
+
+[sqlite]
+usegmtime=no
+loguniqueid=yes
+loguserfield=yes
diff --git a/Calls/asterisk/cel.conf b/Calls/asterisk/cel.conf
new file mode 100644
index 0000000..6622cdb
--- /dev/null
+++ b/Calls/asterisk/cel.conf
@@ -0,0 +1,8 @@
+[general]
+enable=no
+events=BRIDGE_ENTER,BRIDGE_EXIT
+dateformat = %F %T
+
+[manager]
+enabled = yes
+
diff --git a/Calls/asterisk/codecs.conf b/Calls/asterisk/codecs.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/confbridge.conf b/Calls/asterisk/confbridge.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/features.conf b/Calls/asterisk/features.conf
new file mode 100644
index 0000000..dc8f83e
--- /dev/null
+++ b/Calls/asterisk/features.conf
@@ -0,0 +1,12 @@
+[general]
+featuredigittimeout = 2500
+atxfernoanswertimeout = 45
+transferdigittimeout = 3
+pickupexten = *80000
+atxferabort = *00000
+
+[featuremap]
+atxfer => ######
+disconnect = *000000
+blindxfer => **00000
+parkcall => *20000
diff --git a/Calls/asterisk/indications.conf b/Calls/asterisk/indications.conf
new file mode 100644
index 0000000..c730ca9
--- /dev/null
+++ b/Calls/asterisk/indications.conf
@@ -0,0 +1,736 @@
+;
+; indications.conf
+;
+; Configuration file for location specific tone indications
+;
+
+;
+; NOTE:
+; When adding countries to this file, please keep them in alphabetical
+; order according to the 2-character country codes!
+;
+; The [general] category is for certain global variables.
+; All other categories are interpreted as location specific indications
+;
+
+[general]
+country=ru
+
+
+; [example]
+; description = string
+; The full name of your country, in English.
+; ringcadence = num[,num]*
+; List of durations the physical bell rings.
+; dial = tonelist
+; Set of tones to be played when one picks up the hook.
+; busy = tonelist
+; Set of tones played when the receiving end is busy.
+; congestion = tonelist
+; Set of tones played when there is some congestion (on the network?)
+; callwaiting = tonelist
+; Set of tones played when there is a call waiting in the background.
+; dialrecall = tonelist
+; Not well defined; many phone systems play a recall dial tone after hook
+; flash.
+; record = tonelist
+; Set of tones played when call recording is in progress.
+; info = tonelist
+; Set of tones played with special information messages (e.g., "number is
+; out of service")
+; name = tonelist
+; Every other variable will be available as a shortcut for the "PlayList" command
+; but will not be used automatically by Asterisk.
+;
+;
+; The tonelist itself is defined by a comma-separated sequence of elements.
+; Each element consist of a frequency (f) with an optional duration (in ms)
+; attached to it (f/duration). The frequency component may be a mixture of two
+; frequencies (f1+f2) or a frequency modulated by another frequency (f1*f2).
+; The implicit modulation depth is fixed at 90%, though.
+; If the list element starts with a !, that element is NOT repeated,
+; therefore, only if all elements start with !, the tonelist is time-limited,
+; all others will repeat indefinitely.
+;
+; concisely:
+; element = [!]freq[+|*freq2][/duration]
+; tonelist = element[,element]*
+;
+
+[at]
+description = Austria
+ringcadence = 1000,5000
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+dial = 420
+busy = 420/400,0/400
+ring = 420/1000,0/5000
+congestion = 420/200,0/200
+callwaiting = 420/40,0/1960
+dialrecall = 420
+; RECORDTONE - not specified
+record = 1400/80,0/14920
+info = 950/330,1450/330,1850/330,0/1000
+stutter = 380+420
+
+[au]
+description = Australia
+; Reference http://www.acif.org.au/__data/page/3303/S002_2001.pdf
+; Normal Ring
+ringcadence = 400,200,400,2000
+; Distinctive Ring 1 - Forwarded Calls
+; 400,400,200,200,400,1400
+; Distinctive Ring 2 - Selective Ring 2 + Operator + Recall
+; 400,400,200,2000
+; Distinctive Ring 3 - Multiple Subscriber Number 1
+; 200,200,400,2200
+; Distinctive Ring 4 - Selective Ring 1 + Centrex
+; 400,2600
+; Distinctive Ring 5 - Selective Ring 3
+; 400,400,200,400,200,1400
+; Distinctive Ring 6 - Multiple Subscriber Number 2
+; 200,400,200,200,400,1600
+; Distinctive Ring 7 - Multiple Subscriber Number 3 + Data Privacy
+; 200,400,200,400,200,1600
+; Tones
+dial = 413+438
+busy = 425/375,0/375
+ring = 413+438/400,0/200,413+438/400,0/2000
+; XXX Congestion: Should reduce by 10 db every other cadence XXX
+congestion = 425/375,0/375,420/375,0/375
+callwaiting = 425/200,0/200,425/200,0/4400
+dialrecall = 413+438
+; Record tone used for Call Intrusion/Recording or Conference
+record = !425/1000,!0/15000,425/360,0/15000
+info = 425/2500,0/500
+; Other Australian Tones
+; The STD "pips" indicate the call is not an untimed local call
+std = !525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100,!0/100,!525/100
+; Facility confirmation tone (eg. Call Forward Activated)
+facility = 425
+; Message Waiting "stutter" dialtone
+stutter = 413+438/100,0/40
+; Ringtone for calls to Telstra mobiles
+ringmobile = 400+450/400,0/200,400+450/400,0/2000
+
+[bg]
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+description = Bulgaria
+ringcadence = 1000,4000
+;
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/250,0/250
+callwaiting = 425/150,0/150,425/150,0/4000
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+record = 1400/425,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+stutter = 425/1500,0/100
+
+[br]
+description = Brazil
+ringcadence = 1000,4000
+dial = 425
+busy = 425/250,0/250
+ring = 425/1000,0/4000
+congestion = 425/250,0/250,425/750,0/250
+callwaiting = 425/50,0/1000
+; Dialrecall not used in Brazil standard (using UK standard)
+dialrecall = 350+440
+; Record tone is not used in Brazil, use busy tone
+record = 425/250,0/250
+; Info not used in Brazil standard (using UK standard)
+info = 950/330,1400/330,1800/330
+stutter = 350+440
+
+[be]
+description = Belgium
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,3000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/3000
+congestion = 425/167,0/167
+callwaiting = 1400/175,0/175,1400/175,0/3500
+; DIALRECALL - not specified
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+info = 900/330,1400/330,1800/330,0/1000
+stutter = 425/1000,0/250
+
+[ch]
+description = Switzerland
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/200,0/200,425/200,0/4000
+; DIALRECALL - not specified
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+; RECORDTONE - not specified
+record = 1400/80,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+stutter = 425+340/1100,0/1100
+
+[cl]
+description = Chile
+; According to specs from Telefonica CTC Chile
+ringcadence = 1000,3000
+dial = 400
+busy = 400/500,0/500
+ring = 400/1000,0/3000
+congestion = 400/200,0/200
+callwaiting = 400/250,0/8750
+dialrecall = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
+record = 1400/500,0/15000
+info = 950/333,1400/333,1800/333,0/1000
+stutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
+
+[cn]
+description = China
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 450
+busy = 450/350,0/350
+ring = 450/1000,0/4000
+congestion = 450/700,0/700
+callwaiting = 450/400,0/4000
+dialrecall = 450
+record = 950/400,0/10000
+info = 450/100,0/100,450/100,0/100,450/100,0/100,450/400,0/400
+; STUTTER - not specified
+stutter = 450+425
+
+[cz]
+description = Czech Republic
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425/330,0/330,425/660,0/660
+busy = 425/330,0/330
+ring = 425/1000,0/4000
+congestion = 425/165,0/165
+callwaiting = 425/330,0/9000
+; DIALRECALL - not specified
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425/330,0/330,425/660,0/660
+; RECORDTONE - not specified
+record = 1400/500,0/14000
+info = 950/330,0/30,1400/330,0/30,1800/330,0/1000
+; STUTTER - not specified
+stutter = 425/450,0/50
+
+[de]
+description = Germany
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425
+busy = 425/480,0/480
+ring = 425/1000,0/4000
+congestion = 425/240,0/240
+callwaiting = !425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,!0/5000,!425/200,!0/200,!425/200,0
+; DIALRECALL - not specified
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+; RECORDTONE - not specified
+record = 1400/80,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+stutter = 425+400
+
+[dk]
+description = Denmark
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = !425/200,!0/600,!425/200,!0/3000,!425/200,!0/200,!425/200,0
+; DIALRECALL - not specified
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+; RECORDTONE - not specified
+record = 1400/80,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+; STUTTER - not specified
+stutter = 425/450,0/50
+
+[ee]
+description = Estonia
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425
+busy = 425/300,0/300
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+; CALLWAIT not in accordance to ITU
+callwaiting = 950/650,0/325,950/325,0/30,1400/1300,0/2600
+; DIALRECALL - not specified
+dialrecall = 425/650,0/25
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+; INFO not in accordance to ITU
+info = 950/650,0/325,950/325,0/30,1400/1300,0/2600
+; STUTTER not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[es]
+description = Spain
+ringcadence = 1500,3000
+dial = 425
+busy = 425/200,0/200
+ring = 425/1500,0/3000
+congestion = 425/200,0/200,425/200,0/200,425/200,0/600
+callwaiting = 425/175,0/175,425/175,0/3500
+dialrecall = !425/200,!0/200,!425/200,!0/200,!425/200,!0/200,425
+record = 1400/500,0/15000
+info = 950/330,0/1000
+dialout = 500
+; STUTTER not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+
+[fi]
+description = Finland
+ringcadence = 1000,4000
+dial = 425
+busy = 425/300,0/300
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/150,0/150,425/150,0/8000
+dialrecall = 425/650,0/25
+record = 1400/500,0/15000
+info = 950/650,0/325,950/325,0/30,1400/1300,0/2600
+stutter = 425/650,0/25
+
+[fr]
+description = France
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1500,3500
+; Dialtone can also be 440+330
+dial = 440
+busy = 440/500,0/500
+ring = 440/1500,0/3500
+; CONGESTION - not specified
+congestion = 440/250,0/250
+callwait = 440/300,0/10000
+; DIALRECALL - not specified
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330
+stutter = !440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,!440/100,!0/100,440
+
+[gr]
+description = Greece
+ringcadence = 1000,4000
+dial = 425/200,0/300,425/700,0/800
+busy = 425/300,0/300
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/150,0/150,425/150,0/8000
+dialrecall = 425/650,0/25
+record = 1400/400,0/15000
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
+stutter = 425/650,0/25
+
+[hu]
+description = Hungary
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1250,3750
+dial = 425
+busy = 425/300,0/300
+ring = 425/1250,0/3750
+congestion = 425/300,0/300
+callwaiting = 425/40,0/1960
+dialrecall = 425+450
+; RECORDTONE - not specified
+record = 1400/400,0/15000
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
+stutter = 350+375+400
+
+[il]
+description = Israel
+ringcadence = 1000,3000
+dial = 414
+busy = 414/500,0/500
+ring = 414/1000,0/3000
+congestion = 414/250,0/250
+callwaiting = 414/100,0/100,414/100,0/100,414/600,0/3000
+dialrecall = !414/100,!0/100,!414/100,!0/100,!414/100,!0/100,414
+record = 1400/500,0/15000
+info = 1000/330,1400/330,1800/330,0/1000
+stutter = !414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,!414/160,!0/160,414
+
+
+[in]
+description = India
+ringcadence = 400,200,400,2000
+dial = 400*25
+busy = 400/750,0/750
+ring = 400*25/400,0/200,400*25/400,0/2000
+congestion = 400/250,0/250
+callwaiting = 400/200,0/100,400/200,0/7500
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,0/1000
+stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,400*25
+
+[it]
+description = Italy
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+dial = 425/200,0/200,425/600,0/1000
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/400,0/100,425/250,0/100,425/150,0/14000
+dialrecall = 470/400,425/400
+record = 1400/400,0/15000
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
+stutter = 470/400,425/400
+
+[lt]
+description = Lithuania
+ringcadence = 1000,4000
+dial = 425
+busy = 425/350,0/350
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/150,0/150,425/150,0/4000
+; DIALRECALL - not specified
+dialrecall = 425/500,0/50
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
+; STUTTER - not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[jp]
+description = Japan
+ringcadence = 1000,2000
+dial = 400
+busy = 400/500,0/500
+ring = 400+15/1000,0/2000
+congestion = 400/500,0/500
+callwaiting = 400+16/500,0/8000
+dialrecall = !400/200,!0/200,!400/200,!0/200,!400/200,!0/200,400
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,0
+stutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
+
+[mx]
+description = Mexico
+ringcadence = 2000,4000
+dial = 425
+busy = 425/250,0/250
+ring = 425/1000,0/4000
+congestion = 425/250,0/250
+callwaiting = 425/200,0/600,425/200,0/10000
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+record = 1400/500,0/15000
+info = 950/330,0/30,1400/330,0/30,1800/330,0/1000
+stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,425
+
+[my]
+description = Malaysia
+ringcadence = 2000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/400,0/200,425/400,0/2000
+congestion = 425/500,0/500
+; STUTTER - not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[nl]
+description = Netherlands
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+ringcadence = 1000,4000
+; Most of these 425 s can also be 450 s
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/250,0/250
+callwaiting = 425/500,0/9500
+; DIALRECALL - not specified
+dialrecall = 425/500,0/50
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+stutter = 425/500,0/50
+
+[no]
+description = Norway
+ringcadence = 1000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/200,0/200
+callwaiting = 425/200,0/600,425/200,0/10000
+dialrecall = 470/400,425/400
+record = 1400/400,0/15000
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,0
+stutter = 470/400,425/400
+
+[nz]
+description = New Zealand
+; Reference = http://www.telepermit.co.nz/TNA102.pdf
+ringcadence = 400,200,400,2000
+dial = 400
+busy = 400/500,0/500
+ring = 400+450/400,0/200,400+450/400,0/2000
+congestion = 400/250,0/250
+callwaiting = !400/200,!0/3000,!400/200,!0/3000,!400/200,!0/3000,!400/200
+dialrecall = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
+record = 1400/425,0/15000
+info = 400/750,0/100,400/750,0/100,400/750,0/100,400/750,0/400
+stutter = !400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,!400/100,!0/100,400
+unobtainable = 400/75,0/100,400/75,0/100,400/75,0/100,400/75,0/400
+
+[ph]
+
+; reference http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+
+description = Philippines
+ringcadence = 1000,4000
+dial = 425
+busy = 480+620/500,0/500
+ring = 425+480/1000,0/4000
+congestion = 480+620/250,0/250
+callwaiting = 440/300,0/10000
+; DIALRECALL - not specified
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+; INFO - not specified
+info = !950/330,!1400/330,!1800/330,0
+; STUTTER - not specified
+stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,425
+
+
+[pl]
+description = Poland
+ringcadence = 1000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/500,0/500
+callwaiting = 425/150,0/150,425/150,0/4000
+; DIALRECALL - not specified
+dialrecall = 425/500,0/50
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+; 950/1400/1800 3x0.33 on 1.0 off repeated 3 times
+info = !950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000,!950/330,!1400/330,!1800/330,!0/1000
+; STUTTER - not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[pt]
+description = Portugal
+ringcadence = 1000,5000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/5000
+congestion = 425/200,0/200
+callwaiting = 440/300,0/10000
+dialrecall = 425/1000,0/200
+record = 1400/500,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[ru]
+; References:
+; http://www.minsvyaz.ru/site.shtml?id=1806
+; http://www.aboutphone.info/lib/gost/45-223-2001.html
+description = Russian Federation / ex Soviet Union
+ringcadence = 1000,4000
+dial = 425
+busy = 425/350,0/350
+ring = 425/1000,0/4000
+congestion = 425/175,0/175
+callwaiting = 425/200,0/5000
+record = 1400/400,0/15000
+info = 950/330,1400/330,1800/330,0/1000
+dialrecall = 425/400,0/40
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+[se]
+description = Sweden
+ringcadence = 1000,5000
+dial = 425
+busy = 425/250,0/250
+ring = 425/1000,0/5000
+congestion = 425/250,0/750
+callwaiting = 425/200,0/500,425/200,0/9100
+dialrecall = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+record = 1400/500,0/15000
+info = !950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,!0/2024,!950/332,!0/24,!1400/332,!0/24,!1800/332,0
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+; stutter = 425/320,0/20 ; Real swedish standard, not used for now
+
+[sg]
+description = Singapore
+; Singapore
+; Reference: http://www.ida.gov.sg/idaweb/doc/download/I397/ida_ts_pstn1_i4r2.pdf
+; Frequency specs are: 425 Hz +/- 20Hz; 24 Hz +/- 2Hz; modulation depth 100%; SIT +/- 50Hz
+ringcadence = 400,200,400,2000
+dial = 425
+ring = 425*24/400,0/200,425*24/400,0/2000 ; modulation should be 100%, not 90%
+busy = 425/750,0/750
+congestion = 425/250,0/250
+callwaiting = 425*24/300,0/200,425*24/300,0/3200
+stutter = !425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,!425/200,!0/200,!425/600,!0/200,425
+info = 950/330,1400/330,1800/330,0/1000 ; not currently in use acc. to reference
+dialrecall = 425*24/500,0/500,425/500,0/2500 ; unspecified in IDA reference, use repeating Holding Tone A,B
+record = 1400/500,0/15000 ; unspecified in IDA reference, use 0.5s tone every 15s
+; additionally defined in reference
+nutone = 425/2500,0/500
+intrusion = 425/250,0/2000
+warning = 425/624,0/4376 ; end of period tone, warning
+acceptance = 425/125,0/125
+holdinga = !425*24/500,!0/500 ; followed by holdingb
+holdingb = !425/500,!0/2500
+
+[th]
+description = Thailand
+ringcadence = 1000,4000
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+dial = 400*50
+busy = 400/500,0/500
+ring = 420/1000,0/5000
+congestion = 400/300,0/300
+callwaiting = 1000/400,10000/400,1000/400
+; DIALRECALL - not specified - use special dial tone instead.
+dialrecall = 400*50/400,0/100,400*50/400,0/100
+; RECORDTONE - not specified
+record = 1400/500,0/15000
+; INFO - specified as an announcement - use special information tones instead
+info = 950/330,1400/330,1800/330
+; STUTTER - not specified
+stutter = !400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,!400/200,!0/200,!400/600,!0/200,400
+
+[uk]
+description = United Kingdom
+ringcadence = 400,200,400,2000
+; These are the official tones taken from BT SIN350. The actual tones
+; used by BT include some volume differences so sound slightly different
+; from Asterisk-generated ones.
+dial = 350+440
+; Special dial is the intermittent dial tone heard when, for example,
+; you have a divert active on the line
+specialdial = 350+440/750,440/750
+; Busy is also called "Engaged"
+busy = 400/375,0/375
+; "Congestion" is the Beep-bip engaged tone
+congestion = 400/400,0/350,400/225,0/525
+; "Special Congestion" is not used by BT very often if at all
+specialcongestion = 400/200,1004/300
+unobtainable = 400
+ring = 400+450/400,0/200,400+450/400,0/2000
+callwaiting = 400/100,0/4000
+; BT seem to use "Special Call Waiting" rather than just "Call Waiting" tones
+specialcallwaiting = 400/250,0/250,400/250,0/250,400/250,0/5000
+; "Pips" used by BT on payphones. (Sounds wrong, but this is what BT claim it
+; is and I ve not used a payphone for years)
+creditexpired = 400/125,0/125
+; These two are used to confirm/reject service requests on exchanges that
+; don t do voice announcements.
+confirm = 1400
+switching = 400/200,0/400,400/2000,0/400
+; This is the three rising tones Doo-dah-dee "Special Information Tone",
+; usually followed by the BT woman saying an appropriate message.
+info = 950/330,0/15,1400/330,0/15,1800/330,0/1000
+; Not listed in SIN350
+record = 1400/500,0/60000
+stutter = 350+440/750,440/750
+
+[us]
+description = United States / North America
+ringcadence = 2000,4000
+dial = 350+440
+busy = 480+620/500,0/500
+ring = 440+480/2000,0/4000
+congestion = 480+620/250,0/250
+callwaiting = 440/300,0/10000
+dialrecall = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,0
+stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+
+[us-old]
+description = United States Circa 1950/ North America
+ringcadence = 2000,4000
+dial = 600*120
+busy = 500*100/500,0/500
+ring = 420*40/2000,0/4000
+congestion = 500*100/250,0/250
+callwaiting = 440/300,0/10000
+dialrecall = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,0
+stutter = !600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,!600*120/100,!0/100,600*120
+
+[tw]
+description = Taiwan
+; http://nemesis.lonestar.org/reference/telecom/signaling/dialtone.html
+; http://nemesis.lonestar.org/reference/telecom/signaling/busy.html
+; http://www.iproducts.com.tw/ee/kylink/06ky-1000a.htm
+; http://www.pbx-manufacturer.com/ky120dx.htm
+; http://www.nettwerked.net/tones.txt
+; http://www.cisco.com/univercd/cc/td/doc/product/tel_pswt/vco_prod/taiw_sup/taiw2.htm
+;
+; busy tone 480+620Hz 0.5 sec. on ,0.5 sec. off
+; reorder tone 480+620Hz 0.25 sec. on,0.25 sec. off
+; ringing tone 440+480Hz 1 sec. on ,2 sec. off
+;
+ringcadence = 1000,4000
+dial = 350+440
+busy = 480+620/500,0/500
+ring = 440+480/1000,0/2000
+congestion = 480+620/250,0/250
+callwaiting = 350+440/250,0/250,350+440/250,0/3250
+dialrecall = 300/1500,0/500
+record = 1400/500,0/15000
+info = !950/330,!1400/330,!1800/330,0
+stutter = !350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,!350+440/100,!0/100,350+440
+
+[ve]
+; Tone definition source for ve found on
+; Reference: http://www.itu.int/ITU-T/inr/forms/files/tones-0203.pdf
+description = Venezuela / South America
+ringcadence = 1000,4000
+dial = 425
+busy = 425/500,0/500
+ring = 425/1000,0/4000
+congestion = 425/250,0/250
+callwaiting = 400+450/300,0/6000
+dialrecall = 425
+record = 1400/500,0/15000
+info = !950/330,!1440/330,!1800/330,0/1000
+; STUTTER - not specified
+stutter = !425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,!425/100,!0/100,425
+
+
+[za]
+description = South Africa
+; http://www.cisco.com/univercd/cc/td/doc/product/tel_pswt/vco_prod/safr_sup/saf02.htm
+; (definitions for other countries can also be found there)
+; Note, though, that South Africa uses two switch types in their network --
+; Alcatel switches -- mainly in the Western Cape, and Siemens elsewhere.
+; The former use 383+417 in dial, ringback etc. The latter use 400*33
+; I ve provided both, uncomment the ones you prefer
+ringcadence = 400,200,400,2000
+; dial/ring/callwaiting for the Siemens switches:
+dial = 400*33
+ring = 400*33/400,0/200,400*33/400,0/2000
+callwaiting = 400*33/250,0/250,400*33/250,0/250,400*33/250,0/250,400*33/250,0/250
+; dial/ring/callwaiting for the Alcatel switches:
+; dial = 383+417
+; ring = 383+417/400,0/200,383+417/400,0/2000
+; callwaiting = 383+417/250,0/250,383+417/250,0/250,383+417/250,0/250,383+417/250,0/250
+congestion = 400/250,0/250
+busy = 400/500,0/500
+dialrecall = 350+440
+; XXX Not sure about the RECORDTONE
+record = 1400/500,0/10000
+info = 950/330,1400/330,1800/330,0/330
+stutter = !400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,!400*33/100,!0/100,400*33
diff --git a/Calls/asterisk/keys/empty b/Calls/asterisk/keys/empty
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/logger.conf b/Calls/asterisk/logger.conf
new file mode 100644
index 0000000..518e94f
--- /dev/null
+++ b/Calls/asterisk/logger.conf
@@ -0,0 +1,8 @@
+[general]
+queue_log = no
+dateformat = %F %T
+
+[logfiles]
+console => error,verbose(3)
+full => notice,warning,error,debug,verbose(3),dtmf,fax
+
diff --git a/Calls/asterisk/manager.conf b/Calls/asterisk/manager.conf
new file mode 100644
index 0000000..d116b88
--- /dev/null
+++ b/Calls/asterisk/manager.conf
@@ -0,0 +1,17 @@
+[general]
+enabled = yes
+port = 55039
+bindaddr = 0.0.0.0
+displayconnects = no
+allowmultiplelogin = yes
+webenabled = yes
+timestampevents = yes
+httptimeout = 60
+
+[phpagi]
+secret=phpagi
+deny=0.0.0.0/0.0.0.0
+permit=127.0.0.1/255.255.255.255
+read=all
+write=all
+eventfilter=!Event: Newexten
\ No newline at end of file
diff --git a/Calls/asterisk/modules.conf b/Calls/asterisk/modules.conf
new file mode 100644
index 0000000..20802d9
--- /dev/null
+++ b/Calls/asterisk/modules.conf
@@ -0,0 +1,113 @@
+[modules]
+autoload=no
+load => app_dial.so
+load => app_verbose.so
+load => app_exec.so
+load => app_playback.so
+load => codec_alaw.so
+load => codec_g722.so
+load => codec_g726.so
+load => codec_gsm.so
+load => codec_ulaw.so
+load => codec_adpcm.so
+load => codec_speex.so
+load => codec_opus.so
+load => codec_resample.so
+load => format_ogg_speex.so
+load => format_gsm.so
+load => format_pcm.so
+load => format_wav.so
+load => format_wav_gsm.so
+load => format_ogg_vorbis.so
+load => format_mp3.so
+load => format_ogg_opus.so
+load => format_g726.so
+load => format_h263.so
+load => format_h264.so
+load => format_g723.so
+load => format_g719.so
+load => format_sln.so
+load => func_callerid.so
+load => func_export.so
+load => func_speex.so
+load => func_channel.so
+load => func_config.so
+load => func_cut.so
+load => func_cdr.so
+load => func_devstate.so
+load => func_db.so
+load => func_logic.so
+load => func_strings.so
+load => func_pjsip_contact.so
+load => func_pjsip_aor.so
+load => func_rand.so
+load => pbx_config.so
+load => pbx_loopback.so
+load => pbx_spool.so
+load => res_agi.so
+load => res_limit.so
+load => res_musiconhold.so
+load => res_rtp_asterisk.so
+load => res_convert.so
+load => res_timing_timerfd.so
+load => res_timing_pthread.so
+load => res_mutestream.so
+load => func_timeout.so
+load => bridge_simple.so
+load => bridge_holding.so
+load => bridge_builtin_features.so
+load => bridge_builtin_interval_features.so
+load => bridge_softmix.so
+load => app_mp3.so
+load => app_stack.so
+load => func_dialplan.so
+load => res_crypto.so
+load => res_pjproject.so
+load => res_speech.so
+load => res_sorcery_astdb.so
+load => res_sorcery_config.so
+load => res_sorcery_memory.so
+load => chan_pjsip.so
+load => func_pjsip_endpoint.so
+load => res_http_websocket.so
+load => res_musiconhold.so
+load => res_pjproject.so
+load => res_pjsip_acl.so
+load => res_pjsip_authenticator_digest.so
+load => res_pjsip_caller_id.so
+load => res_pjsip_dialog_info_body_generator.so
+load => res_pjsip_diversion.so
+load => res_pjsip_dtmf_info.so
+load => res_pjsip_endpoint_identifier_anonymous.so
+load => res_pjsip_endpoint_identifier_ip.so
+load => res_pjsip_endpoint_identifier_user.so
+load => res_pjsip_exten_state.so
+load => res_pjsip_header_funcs.so
+load => res_pjsip_logger.so
+load => res_pjsip_messaging.so
+load => res_pjsip_mwi_body_generator.so
+load => res_pjsip_mwi.so
+load => res_pjsip_nat.so
+load => res_pjsip_notify.so
+load => res_pjsip_one_touch_record_info.so
+load => res_pjsip_outbound_authenticator_digest.so
+load => res_pjsip_outbound_publish.so
+load => res_pjsip_outbound_registration.so
+load => res_pjsip_path.so
+load => res_pjsip_pidf_body_generator.so
+load => res_pjsip_pidf_digium_body_supplement.so
+load => res_pjsip_pidf_eyebeam_body_supplement.so
+load => res_pjsip_publish_asterisk.so
+load => res_pjsip_pubsub.so
+load => res_pjsip_refer.so
+load => res_pjsip_registrar.so
+load => res_pjsip_rfc3326.so
+load => res_pjsip_sdp_rtp.so
+load => res_pjsip_send_to_voicemail.so
+load => res_pjsip_session.so
+load => res_pjsip.so
+load => res_pjsip_t38.so
+load => res_pjsip_transport_websocket.so
+load => res_pjsip_xpidf_body_generator.so
+load => res_pjsip_dlg_options.so
+load => res_security_log.so
diff --git a/Calls/asterisk/musiconhold.conf b/Calls/asterisk/musiconhold.conf
new file mode 100644
index 0000000..499cf65
--- /dev/null
+++ b/Calls/asterisk/musiconhold.conf
@@ -0,0 +1,4 @@
+[default]
+mode=files
+directory=/storage/usbdisk1/mikopbx/media/moh
+
diff --git a/Calls/asterisk/pjproject.conf b/Calls/asterisk/pjproject.conf
new file mode 100644
index 0000000..85f68d0
--- /dev/null
+++ b/Calls/asterisk/pjproject.conf
@@ -0,0 +1,6 @@
+[log_mappings]
+type=log_mappings
+asterisk_error = 0
+asterisk_warning = 2
+asterisk_debug = 1,3,4,5,6
+
diff --git a/Calls/asterisk/pjsip_notify.conf b/Calls/asterisk/pjsip_notify.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/queuerules.conf b/Calls/asterisk/queuerules.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/queues.conf b/Calls/asterisk/queues.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/rtp.conf b/Calls/asterisk/rtp.conf
new file mode 100644
index 0000000..f7e68fa
--- /dev/null
+++ b/Calls/asterisk/rtp.conf
@@ -0,0 +1,4 @@
+[general]
+rtpstart=40000
+rtpend=40900
+
diff --git a/Calls/asterisk/sorcery.conf b/Calls/asterisk/sorcery.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/asterisk/udptl.conf b/Calls/asterisk/udptl.conf
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/db/empty b/Calls/db/empty
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/persistence/log b/Calls/persistence/log
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/run/log b/Calls/run/log
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/spool/log b/Calls/spool/log
new file mode 100644
index 0000000..e69de29
diff --git a/Calls/templates/asterisk-pattern.conf b/Calls/templates/asterisk-pattern.conf
new file mode 100644
index 0000000..d5fd36e
--- /dev/null
+++ b/Calls/templates/asterisk-pattern.conf
@@ -0,0 +1,21 @@
+[directories]
+astetcdir => PATH/asterisk
+astagidir => PATH/asterisk/agi-bin
+astkeydir => PATH/asterisk
+astrundir => PATH/run
+astmoddir => /offload/asterisk/modules
+astvarlibdir => /offload/asterisk
+astdbdir => PATH/persistence
+astlogdir => LOG_DIR/logs
+astspooldir => PATH/logs/spool/
+astdatadir => /offload/asterisk
+
+
+[options]
+verbose = 0
+debug = 0
+dumpcore = no
+transcode_via_sln = no
+hideconnect = yes
+defaultlanguage = ru-ru
+systemname = mikopbx
diff --git a/Calls/templates/extensions b/Calls/templates/extensions
new file mode 100644
index 0000000..f04a3ab
--- /dev/null
+++ b/Calls/templates/extensions
@@ -0,0 +1,33 @@
+[globals]
+[general]
+[incoming]
+exten => _.!,1,Dial(PJSIP/${EXTEN}@${CALLERID(num)},600,Tt)
+exten => _[hit],1,Hangup()
+exten => 203,1,Goto(incoming-to-sip2,${EXTEN},1)
+exten => 201,1,Goto(incoming-to-sip2,${EXTEN},1)
+
+[incoming-to-sip2]
+exten => _X!,1,Set(_SRC_CHAN=${CHANNEL})
+ same => n,Set(_SRC_CID=${CALLERID(num)})
+ same => n,Set(_SRC_DST=${EXTEN})
+ same => n,Dial(PJSIP/${EXTEN}@sip2,,f(${SRC_CID} )Ttb(create-dst-chan,${SRC_DST},1)G(orig-call^${SRC_CID}^1))
+
+[orig-call]
+exten => _X!,1,Goto(orig-src-chan,${EXTEN},1)
+exten => _X!,2,Goto(orig-dst-chan,${EXTEN},1)
+[create-dst-chan]
+exten => _X!,1,Set(EXPORT(${SRC_CHAN},DST_CHAN)=${CHANNEL})
+ same => n,Set(DST_CHAN=${CHANNEL})
+ same => n,return
+[orig-src-chan]
+exten => _X!,1,NoOp(is SRC chan, DST_CHAN: ${DST_CHAN}, id=${CHANNEL(linkedid)}, SRC_CID=${SRC_CID})
+ same => n,Wait(600)
+exten => h,1,AGI(/storage/usbdisk1/mikopbx/custom_modules/ModuleConnectorFMC/agi-bin/hangup.php,${DST_CHAN})
+[orig-dst-chan]
+exten => _X!,1,NoOp(is DST chan, SRC_CHAN: ${SRC_CHAN}, id=${CHANNEL(linkedid)}, SRC_CID=${SRC_CID}, SRC_DST=${SRC_DST})
+ same => n,AGI(/storage/usbdisk1/mikopbx/custom_modules/ModuleConnectorFMC/agi-bin/save-state.php)
+ same => n,Wait(600)
+exten => h,1,AGI(/storage/usbdisk1/mikopbx/custom_modules/ModuleConnectorFMC/agi-bin/hangup.php,${SRC_CHAN})
+[orig-src-bridge]
+exten => s,1,Answer()
+ same => n,Bridge(${IF($["${CHANNEL}" == "${DST_CHAN}"]?${SRC_CHAN}:${DST_CHAN})},Tt)
diff --git a/Calls/templates/manager.conf b/Calls/templates/manager.conf
new file mode 100644
index 0000000..1cb7863
--- /dev/null
+++ b/Calls/templates/manager.conf
@@ -0,0 +1,17 @@
+[general]
+enabled = yes
+port =
+bindaddr = 0.0.0.0
+displayconnects = no
+allowmultiplelogin = yes
+webenabled = yes
+timestampevents = yes
+httptimeout = 60
+
+[phpagi]
+secret=phpagi
+deny=0.0.0.0/0.0.0.0
+permit=127.0.0.1/255.255.255.255
+read=all
+write=all
+eventfilter=!Event: Newexten
\ No newline at end of file
diff --git a/Calls/templates/pjsip-pattern-endpoint.conf b/Calls/templates/pjsip-pattern-endpoint.conf
new file mode 100644
index 0000000..1859802
--- /dev/null
+++ b/Calls/templates/pjsip-pattern-endpoint.conf
@@ -0,0 +1,54 @@
+[AUTH-]
+type = auth
+username =
+password =
+
+[-AOR]
+type = aor
+max_contacts = 1
+contact = sip:@:
+maximum_expiration = 3600
+minimum_expiration = 60
+default_expiration = 120
+qualify_frequency = 60
+qualify_timeout = 3.0
+
+[-IDENTIFY]
+type = identify
+endpoint =
+match =
+match_header = To: @>
+
+[REG-]
+type = registration
+outbound_auth = AUTH-
+contact_user =
+retry_interval = 30
+max_retries = 100
+forbidden_retry_interval = 300
+fatal_retry_interval = 300
+expiration = 120
+server_uri = sip::
+client_uri = sip:@:
+
+[]
+type = endpoint
+100rel = no
+dtmf_mode = auto
+disallow = all
+allow = opus
+allow = alaw
+rtp_symmetric = yes
+force_rport = yes
+rewrite_contact = yes
+ice_support = no
+direct_media = no
+sdp_session = mikopbx
+language = ru-ru
+timers = no
+from_user =
+from_domain =
+
+aors = -AOR
+context = incoming
+outbound_auth = AUTH-
\ No newline at end of file
diff --git a/Calls/templates/pjsip-pattern.conf b/Calls/templates/pjsip-pattern.conf
new file mode 100644
index 0000000..6d7aa9f
--- /dev/null
+++ b/Calls/templates/pjsip-pattern.conf
@@ -0,0 +1,14 @@
+[general]
+disable_multi_domain=on
+transport = udp
+
+[global]
+type = global
+endpoint_identifier_order=header,username,ip,anonymous
+user_agent =
+
+[transport-udp]
+type = transport
+protocol = udp
+bind=0.0.0.0:
+
diff --git a/Calls/templates/trunk_pattern.conf b/Calls/templates/trunk_pattern.conf
new file mode 100644
index 0000000..87c6a50
--- /dev/null
+++ b/Calls/templates/trunk_pattern.conf
@@ -0,0 +1,38 @@
+
+[-AUTH]
+type = auth
+username =
+password =
+
+[]
+type = aor
+max_contacts = 5
+maximum_expiration = 3600
+minimum_expiration = 60
+default_expiration = 120
+qualify_frequency = 60
+qualify_timeout = 3.0
+
+[]
+type = endpoint
+100rel = no
+context = -incoming
+dtmf_mode = auto
+disallow = all
+allow = opus
+allow = alaw
+rtp_symmetric = yes
+force_rport = yes
+rewrite_contact = yes
+ice_support = no
+direct_media = no
+contact_user =
+sdp_session = mikopbx
+language = ru-ru
+aors =
+timers = no
+rtp_timeout = 30
+rtp_timeout_hold = 30
+auth = -AUTH
+inband_progress = yes
+tone_zone = ru
\ No newline at end of file
diff --git a/Lib/ConfigureAsterisk.php b/Lib/ConfigureAsterisk.php
new file mode 100644
index 0000000..3147365
--- /dev/null
+++ b/Lib/ConfigureAsterisk.php
@@ -0,0 +1,334 @@
+.
+ */
+
+namespace Modules\ModuleConnectorFMC\Lib;
+use MikoPBX\Common\Models\Extensions;
+use MikoPBX\Common\Models\PbxSettings;
+use MikoPBX\Common\Models\PbxSettingsConstants;
+use MikoPBX\Common\Models\Sip;
+use MikoPBX\Core\System\Util;
+use MikoPBX\Core\System\Processes;
+use Modules\ModuleConnectorFMC\Models\ModuleConnectorFMC;
+use Modules\ModuleConnectorFMC\Models\TrunksFMC;
+use MikoPBX\Modules\PbxExtensionUtils;
+use Phalcon\Mvc\Model\Manager;
+use Phalcon\Mvc\Model\Row;
+
+class ConfigureAsterisk
+{
+ public const EXTENSIONS_CONF_NAME = 'extensions';
+ public const ASTERISK_CONF_NAME = 'asterisk';
+ public const MANAGER_CONF_NAME = 'manager';
+ public const RTP_CONF_NAME = 'rtp';
+ public const PJSIP_CONF_NAME = 'pjsip';
+
+ private Row $settings;
+ private string $sipPort;
+ private array $providers;
+ private string $dirName;
+ private string $agiDir;
+
+ private array $configFileData = [];
+ private string $asteriskConfPath;
+ private string $cmdAsterisk;
+
+ public function __construct()
+ {
+ $settings = ModuleConnectorFMC::findFirst(['columns' => 'sipPort,userAgent,rtpPortStart,rtpPortEnd']);
+ if(!$settings){
+ exit(2);
+ }
+ $this->settings = $settings;
+
+ $this->agiDir = dirname(__DIR__).'/agi-bin';
+ $this->dirName = dirname(__DIR__).'/Calls';
+ $this->asteriskConfPath = "$this->dirName/asterisk/asterisk.conf";
+ $this->cmdAsterisk = Util::which('asterisk');
+ $this->sipPort = PbxSettings::getValueByKey(PbxSettingsConstants::SIP_PORT);
+ $this->providers= TrunksFMC::find(['columns' => 'incomingEndpointHost AS host,incomingEndpointPort AS port,incomingEndpointSecret AS secret,incomingEndpointLogin AS endpoint,extensions,providerType'])->toArray();
+ }
+
+ public function makeConfig($action):void
+ {
+ $pid = Processes::getPidOfProcess($this->asteriskConfPath);
+ if($action === 'stop' || PbxExtensionUtils::isEnabled('ModuleConnectorFMC') !== true){
+ shell_exec(Util::which('kill')." $pid");
+ exit(0);
+ }
+
+ $this->makeAstConf();
+ $this->makeRtp();
+ $this->makePjSip();
+ $this->makeExtensions();
+ $this->makeManagerConf();
+
+ foreach ($this->configFileData as $name => $config){
+ $asteriskRtpConfPath = "$this->dirName/asterisk/$name.conf";
+ $oldConfig = file_get_contents($asteriskRtpConfPath);
+ if($oldConfig !== $config){
+ file_put_contents($asteriskRtpConfPath, $config);
+ if(in_array($action, ['stop', 'restart'], true)){
+ continue;
+ }
+ switch ($name) {
+ case self::ASTERISK_CONF_NAME:
+ // рестарт астера
+ break;
+ case self::EXTENSIONS_CONF_NAME:
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath' -rx 'dialplan reload'");
+ break;
+ case self::MANAGER_CONF_NAME:
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath' -rx 'manager reload'");
+ break;
+ case self::RTP_CONF_NAME:
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath' -rx 'module reload res_rtp_asterisk.so");
+ break;
+ case self::PJSIP_CONF_NAME:
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath' -rx 'module reload res_pjsip.so'");
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath' -rx 'pjsip send register *all'");
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ if(!empty($pid) && 'restart' === $action){
+ shell_exec(Util::which('kill')." $pid");
+ $pid = '';
+ }
+ if(empty($pid)){
+ shell_exec("$this->cmdAsterisk -C '$this->asteriskConfPath'");
+ }
+ }
+
+ private function makeExtensions():void
+ {
+ $extensionsConfAdditional = '';
+ $userExtensions = '';
+ $extensionsConf = "[globals]".PHP_EOL;
+ $extensionsConf.= "[general]".PHP_EOL;
+ $extensionsConf.= "[incoming]".PHP_EOL;
+ $extensionsConf.='exten => _[hit],1,Hangup()'.PHP_EOL;
+ $extensionsConf.='exten => _.!,1,Dial(PJSIP/${EXTEN}@${CALLERID(num)},600,Tt)'.PHP_EOL;
+ $extensionsConf.='exten => _.!,2,Hangup()'.PHP_EOL.PHP_EOL;
+
+ foreach ($this->providers as $provider){
+ if(intval($provider['providerType']) === TrunksFMC::PROVIDER_TYPE_B24){
+ $filterSip = [
+ "type = 'peer' AND disabled <> '1' AND extension IN ({extension:array}) ",
+ 'columns' => 'extension,secret',
+ 'order' => 'extension',
+ 'bind' => [
+ 'extension' => explode(',',$provider['extensions'])
+ ]
+ ];
+ $peers = Sip::find($filterSip);
+ foreach ($peers as $peer){
+ $extensionsConf.= 'exten => '.$peer->extension.',1,Goto(incoming-to-'.$provider['endpoint'].',${EXTEN},1)'.PHP_EOL;
+ // $extensionsConf.= 'exten => '.$peer->extension.',1,Dial(PJSIP/${EXTEN}@'.$provider['endpoint'].',,f(${CALLERID(num)} <'.$provider['endpoint'].'>)Tt)'.PHP_EOL;
+ }
+ $extensionsConfAdditional.= PHP_EOL.'[incoming-to-'.$provider['endpoint'].']' . PHP_EOL .
+ 'exten => _X!,1,Ringing()' . PHP_EOL .
+ ' same => n,Set(_SRC_CHAN=${CHANNEL})' . PHP_EOL .
+ ' same => n,Set(_SRC_CID=${CALLERID(num)})' . PHP_EOL .
+ ' same => n,Set(_SRC_DST=${EXTEN})' . PHP_EOL .
+ ' same => n,Dial(PJSIP/${EXTEN}@'.$provider['endpoint'].',,f(${SRC_CID} <'.$provider['endpoint'].'>)Ttb(create-dst-chan,${SRC_DST},1)G(orig-call^${SRC_CID}^1))' . PHP_EOL;
+ }elseif (intval($provider['providerType']) === TrunksFMC::PROVIDER_TYPE_MCN){
+ $sipPort= PbxSettings::getValueByKey('SIPPort');
+
+ $extensionData = ConfigureAsterisk::getPbxNumbers();
+ $userExtensions.= '[users-extensions-'.$provider['endpoint'].']'.PHP_EOL;
+ foreach ($extensionData as $number => $mobile){
+ $shotMobile = substr($mobile, -10);
+ $extensionsConf.= "; For exten: $number mobile: $mobile".PHP_EOL;
+ $extensionsConf.= "exten => _X!/_X".$shotMobile.",1,Dial(PJSIP/\${EXTEN}@$number,600,Tt)".PHP_EOL;
+ $extensionsConf.= "exten => _X".$shotMobile.",1,Dial(PJSIP/{$provider['endpoint']}/sip:$number@127.0.0.1:$sipPort,600,Tt)".PHP_EOL;
+ $extensionsConf.= "exten => $number,1,Goto(dial-to-fmc-".$provider['endpoint'].",\${EXTEN},1)".PHP_EOL.PHP_EOL;
+
+ $extensionsConf.= "; For outgoing from FMC to all PSTN".PHP_EOL;
+ $extensionsConf.= "exten => _XXXXX!/_$number,1,NoOp()".PHP_EOL;
+ $extensionsConf.= ' same => n,GosubIf($["${DIALPLAN_EXISTS(users-extensions-'.$provider['endpoint'].',${CALLERID(num)},1)}" == "1"]?users-extensions-'.$provider['endpoint'].',${CALLERID(num)},1)'.PHP_EOL;
+ $extensionsConf.= ' same => n,Dial(PJSIP/${EXTEN}@'.$provider['endpoint'].',,f(${MOBILE} <${MOBILE}\;cpc=MT_FMC>)rTtb(create_chan_set_diversion-'.$provider['endpoint'].',s,1)U(dial_answer))'.PHP_EOL;
+
+ $userExtensions.= "exten => $number,1,Set(__MOBILE=$mobile)".PHP_EOL;
+ }
+ $userExtensions.= 'exten => _X!,2,return'.PHP_EOL.PHP_EOL;
+
+ $extensionsConf .= '[dial-to-fmc-'.$provider['endpoint'].']'.PHP_EOL;
+ $extensionsConf .= 'exten => _XX!,1,Progress()'.PHP_EOL;
+ $extensionsConf .= ' same => n,GosubIf($["${DIALPLAN_EXISTS(users-extensions-'.$provider['endpoint'].',${EXTEN},1)}" == "1"]?users-extensions-'.$provider['endpoint'].',${EXTEN},1)'.PHP_EOL;
+ $extensionsConf .= ' same => n,ExecIf($["${MOBILE}x" = "x"]?hangup)'.PHP_EOL;
+ $extensionsConf .= ' same => n,Playback(silence/1,noanswer)'.PHP_EOL;
+ $extensionsConf .= ' same => n,Set(TMP_CID=${CALLERID(num)})'.PHP_EOL;
+ $extensionsConf .= ' same => n,ExecIf($[${LEN(${TMP_CID})} < 5]?Set(TMP_CID=${MOBILE}*B${TMP_CID})) '.PHP_EOL;
+ $extensionsConf .= ' same => n,Dial(PJSIP/${MOBILE}@'.$provider['endpoint'].',,f(${TMP_CID} <${TMP_CID}\;cpc=MT_FMC>)rTtb(create_chan_set_diversion-'.$provider['endpoint'].',s,1)U(dial_answer))'.PHP_EOL;
+ $extensionsConf .= ' same => n,hangup()'.PHP_EOL;
+ $extensionsConf .= PHP_EOL;
+ $extensionsConf .= '[create_chan_set_diversion-'.$provider['endpoint'].']'.PHP_EOL;
+ $extensionsConf .= 'exten => s,1,NoOp(start set diversion)'.PHP_EOL;
+ $extensionsConf .= ' same => n,Set(PJSIP_HEADER(add,Diversion)=\;reason=no-answer)'.PHP_EOL;
+ $extensionsConf .= ' same => n,return'.PHP_EOL.PHP_EOL;
+ }
+ }
+
+ $extensionsConf.=$userExtensions;
+ $extensionsConf .= '[dial_answer]'.PHP_EOL;
+ $extensionsConf .= 'exten => s,1,Playback(beep)'.PHP_EOL;
+ $extensionsConf .= ' same => n,return'.PHP_EOL.PHP_EOL;
+
+ $extensionsConf.=$extensionsConfAdditional;
+ $extensionsConf.=
+ '[orig-call]' . PHP_EOL .
+ 'exten => _X!,1,Goto(orig-src-chan,${EXTEN},1)' . PHP_EOL .
+ 'exten => _X!,2,Goto(orig-dst-chan,${EXTEN},1)' . PHP_EOL .
+ '[create-dst-chan]' . PHP_EOL .
+ 'exten => _X!,1,Set(EXPORT(${SRC_CHAN},DST_CHAN)=${CHANNEL})' . PHP_EOL .
+ ' same => n,Set(DST_CHAN=${CHANNEL})' . PHP_EOL .
+ ' same => n,return' . PHP_EOL .
+ '[orig-src-chan]' . PHP_EOL .
+ 'exten => _X!,1,NoOp(is SRC chan, DST_CHAN: ${DST_CHAN}, id=${CHANNEL(linkedid)}, SRC_CID=${SRC_CID})' . PHP_EOL .
+ ' same => n,Ringing()' . PHP_EOL .
+ ' same => n,Wait(600)' . PHP_EOL .
+ 'exten => h,1,AGI('.$this->agiDir.'/hangup.php,${DST_CHAN})' . PHP_EOL .
+ PHP_EOL .
+ '[orig-dst-chan]' . PHP_EOL .
+ 'exten => _X!,1,NoOp(is DST chan, SRC_CHAN: ${SRC_CHAN}, id=${CHANNEL(linkedid)}, SRC_CID=${SRC_CID}, SRC_DST=${SRC_DST})' . PHP_EOL .
+ ' same => n,Set(VI_CALL_ID=${PJSIP_RESPONSE_HEADER(read,VI-Call-ID)})' . PHP_EOL .
+ ' same => n,Set(EXPORT(${SRC_CHAN},VI_CALL_ID)=${VI_CALL_ID})' . PHP_EOL .
+ ' same => n,AGI('.$this->agiDir.'/save-state.php)' . PHP_EOL .
+ ' same => n,Wait(600)' . PHP_EOL .
+ 'exten => h,1,AGI('.$this->agiDir.'/hangup.php,${SRC_CHAN})' . PHP_EOL .
+ PHP_EOL .
+ '[orig-src-bridge]' . PHP_EOL .
+ 'exten => s,1,Answer()' . PHP_EOL .
+ ' same => n,Bridge(${IF($["${CHANNEL}" == "${DST_CHAN}"]?${SRC_CHAN}:${DST_CHAN})},Tt)'.PHP_EOL.
+ 'exten => h,1,AGI('.$this->agiDir.'/hangup.php)' . PHP_EOL ;
+
+ $this->configFileData[self::EXTENSIONS_CONF_NAME] = $extensionsConf;
+ }
+
+ public static function getPbxNumbers():array
+ {
+ $pbx_numbers = [];
+ $di = MikoPBXVersion::getDefaultDi();
+ /** @var Manager $manager */
+ $manager = $di->get('modelsManager');
+ $parameters = [
+ 'models' => [
+ 'ExtensionsSip' => Extensions::class,
+ ],
+ 'conditions' => 'ExtensionsSip.type = :typeSip:',
+ 'bind' => ['typeSip' => Extensions::TYPE_SIP,'typeExternal' => Extensions::TYPE_EXTERNAL],
+ 'columns' => [
+ 'number' => 'ExtensionsSip.number',
+ 'mobile' => 'ExtensionsExternal.number',
+ 'userid' => 'ExtensionsSip.userid',
+ ],
+ 'order' => 'number',
+ 'joins' => [
+ 'ExtensionsExternal' => [
+ 0 => Extensions::class,
+ 1 => 'ExtensionsSip.userid = ExtensionsExternal.userid AND ExtensionsExternal.type = :typeExternal:',
+ 2 => 'ExtensionsExternal',
+ 3 => 'INNER',
+ ],
+ ],
+ ];
+ $query = $manager->createBuilder($parameters)->getQuery();
+ $result = $query->execute()->toArray();
+ foreach ($result as $data){
+ $pbx_numbers[$data['number']] = $data['mobile'];
+ }
+ return $pbx_numbers;
+ }
+
+ private function makeAstConf():void
+ {
+ $asteriskConfPattern = file_get_contents($this->dirName.'/templates/asterisk-pattern.conf');
+ $logDir = ConnectorFMCConf::getLogDir();
+ Util::mwMkdir($logDir);
+ $asteriskConf = str_replace(array('LOG_DIR', 'PATH'), array($logDir, $this->dirName), $asteriskConfPattern);
+ $this->configFileData[self::ASTERISK_CONF_NAME] = $asteriskConf;
+
+ }
+
+ private function makeManagerConf():void
+ {
+ $pattern = file_get_contents($this->dirName.'/templates/manager.conf');
+ $conf = str_replace('', ConnectorFMCConf::getAmiPort(), $pattern);
+ $this->configFileData[self::MANAGER_CONF_NAME] = $conf;
+ }
+
+ private function makeRtp():void
+ {
+ $rtpConf = "[general]".PHP_EOL.
+ "rtpstart={$this->settings->rtpPortStart}".PHP_EOL.
+ "rtpend={$this->settings->rtpPortEnd}".PHP_EOL;
+
+ $this->configFileData[self::RTP_CONF_NAME] = $rtpConf;
+ }
+
+ private function makePjSip():void
+ {
+ $endpointPattern = file_get_contents($this->dirName.'/templates/pjsip-pattern-endpoint.conf');
+ $configPjSip = str_replace(
+ ['', ''],
+ [$this->settings->userAgent, $this->settings->sipPort],
+ file_get_contents($this->dirName . '/templates/pjsip-pattern.conf')
+ );
+
+ foreach ($this->providers as $provider){
+ if(empty($provider['host'])){
+ continue;
+ }
+ $conf = str_replace(
+ ['', '', '', '', 'from_user'],
+ [
+ $provider['endpoint'],
+ $provider['secret'],
+ $provider['port'],
+ $provider['host'],
+ ';from_user',
+ ],
+ $endpointPattern
+ );
+ $configPjSip .= PHP_EOL.$conf.PHP_EOL;
+
+ $filterSip = [
+ "type = 'peer' AND disabled <> '1' AND extension IN ({extension:array}) ",
+ 'columns' => 'extension,secret',
+ 'order' => 'extension',
+ 'bind' => [
+ 'extension' => explode(',',$provider['extensions'])
+ ]
+ ];
+ $peers = Sip::find($filterSip);
+ foreach ($peers as $peer){
+ $conf = str_replace(
+ ['', '', '', ''],
+ [$peer->extension, $peer->secret, $this->sipPort, '127.0.0.1'],
+ $endpointPattern
+ );
+ $configPjSip .= PHP_EOL.$conf.PHP_EOL;
+ }
+ }
+ $this->configFileData[self::PJSIP_CONF_NAME] = $configPjSip;
+ }
+
+}
\ No newline at end of file
diff --git a/Lib/ConnectorFMCConf.php b/Lib/ConnectorFMCConf.php
new file mode 100644
index 0000000..3a082c7
--- /dev/null
+++ b/Lib/ConnectorFMCConf.php
@@ -0,0 +1,310 @@
+moduleDir.'/bin/init-asterisk.php';
+ $phpPath = Util::which('php');
+ if ( $data['model'] === PbxSettings::class && $data['recordId'] === PbxSettingsConstants::SIP_PORT ) {
+ shell_exec("$phpPath -f $scriptPath");
+ }elseif ( $data['model'] === Sip::class || $data['model'] === ExternalPhones::class) {
+ shell_exec("$phpPath -f $scriptPath");
+ }elseif ( $data['model'] === ModuleConnectorFMC::class && in_array($data['recordId'],['rtpPortStart', 'sipPort', 'rtpPortEnd'], true)) {
+ shell_exec("$phpPath -f $scriptPath restart");
+ PBX::sipReload();
+ PBX::dialplanReload();
+ }elseif ($data['model'] === ModuleConnectorFMC::class || $data['model'] === TrunksFMC::class){
+ shell_exec("$phpPath -f $scriptPath");
+ PBX::sipReload();
+ PBX::dialplanReload();
+ }
+ }
+
+ /**
+ * Prepares additional contexts sections in the extensions.conf file
+ * @see https://docs.mikopbx.com/mikopbx-development/module-developement/module-class#extensiongencontexts
+ *
+ * @return string
+ */
+ public function extensionGenContexts(): string
+ {
+ $conf = '';
+ $settings = ModuleConnectorFMC::findFirst(['columns' => 'sipPort']);
+ if(!$settings){
+ return '';
+ }
+ $trunks = TrunksFMC::find(['columns' => 'outputEndpoint AS id,providerType'])->toArray();
+ foreach ($trunks as $trunk){
+ if(intval($trunk['providerType']) === TrunksFMC::PROVIDER_TYPE_B24){
+ $conf.= '['.$trunk['id'].'-incoming]'.PHP_EOL.
+ 'exten => _.!,1,NoOp()'.PHP_EOL.
+ ' same => n,Set(callId=${PJSIP_HEADER(read,X-Extension-Number)})'.PHP_EOL.
+ ' same => n,Set(CALLERID(num)=${callId})'.PHP_EOL.
+ ' same => n,Set(CALLERID(name)=${callId})'.PHP_EOL.
+ ' same => n,Dial(PJSIP/'.$trunk['id'].'/sip:${EXTEN}@127.0.0.1:'.$settings->sipPort.',,f(${callId} <${callId}>))'.PHP_EOL.
+ 'exten => _[hit],1,Hangup()';
+ }elseif (intval($trunk['providerType']) === TrunksFMC::PROVIDER_TYPE_MCN) {
+ $conf .= '['.$trunk['id'].'-incoming]'.PHP_EOL;
+ $conf .= 'exten => _X!,1,NoOp(--- Incoming call ---)'.PHP_EOL;
+ $conf .= ' same => n,Set(CHANNEL(language)=ru-ru)'.PHP_EOL;
+ $conf .= ' same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'.PHP_EOL;
+ $conf .= ' same => n,Set(__FROM_DID=${EXTEN})'.PHP_EOL;
+ $conf .= ' same => n,Set(__FROM_CHAN=${CHANNEL})'.PHP_EOL;
+ $conf .= ' same => n,Set(__M_CALLID=${CHANNEL(callid)})'.PHP_EOL;
+ $conf .= ' same => n,Set(__TRANSFER_OPTIONS=t)'.PHP_EOL;
+ $conf .= ' same => n,Set(M_TIMEOUT=600)'.PHP_EOL;
+ $conf .= ' same => n,Progress()'.PHP_EOL;
+ $conf .= ' same => n,Playback(silence/1,noanswer)'.PHP_EOL;
+ $conf .= ' same => n,GosubIf($["${DIALPLAN_EXISTS('.$trunk['id'].'-find-did-incoming'.',${EXTEN},1)}" == "1"]?'.$trunk['id'].'-find-did-incoming'.',${EXTEN},1) '.PHP_EOL;
+ $conf .= ' same => n,Dial(Local/did2user@internal-incoming,600,${TRANSFER_OPTIONS}Kg)'.PHP_EOL;
+ $conf .= ' same => n,Hangup()'.PHP_EOL.PHP_EOL;
+
+ $extensionData = ConfigureAsterisk::getPbxNumbers();
+ $conf .= '['.$trunk['id'].'-find-did-incoming]'.PHP_EOL;
+ foreach ($extensionData as $number => $mobile){
+ $conf .= "exten => $number,1,NoOp(--- Incoming call ---)".PHP_EOL;
+ $conf .= ' same => n,GotoIf($["${DIALPLAN_EXISTS(none-incoming,'.$mobile.',1)}" == "1"]?none-incoming,'.$mobile.',1) '.PHP_EOL;
+ $conf .= ' same => n,return'.PHP_EOL;
+ }
+
+ $conf .= '[all-outgoing-'.$trunk['id'].'-custom]'.PHP_EOL;
+ foreach ($extensionData as $number => $mobile){
+ $conf .= "exten => _.X!/$number,1,Dial(PJSIP/$trunk[id]/sip:".'${EXTEN}'."@127.0.0.1:$settings->sipPort,600,TKU(dial_answer)b(dial_create_chan,s,1))".PHP_EOL;
+ }
+ $conf .= 'exten => _.X!,2,Hangup()'.PHP_EOL;
+ }
+ }
+ return $conf;
+ }
+
+ /**
+ * Prepares additional peers data in the pjsip.conf file
+ * @see https://docs.mikopbx.com/mikopbx-development/module-developement/module-class#generatepeerspj
+ *
+ * @return string
+ */
+ public function generatePeersPj(): string
+ {
+ $settings = ModuleConnectorFMC::findFirst(['columns' => 'sipPort']);
+ if(!$settings){
+ return '';
+ }
+ $trunks = TrunksFMC::find(['columns' => 'outputEndpoint AS id,outputEndpointSecret AS pass,providerType'])->toArray();
+ $config = '';
+ foreach ($trunks as $trunk) {
+ if(intval($trunk['providerType']) === TrunksFMC::PROVIDER_TYPE_B24){
+ $config.= "[".$trunk['id']."-AUTH]" . PHP_EOL .
+ "type = auth" . PHP_EOL .
+ "username = ".$trunk['id'] . PHP_EOL .
+ "password = ".$trunk['pass'] . PHP_EOL . PHP_EOL .
+
+ "[".$trunk['id']."]" . PHP_EOL .
+ "type = aor" . PHP_EOL .
+ "max_contacts = 5" . PHP_EOL .
+ "maximum_expiration = 3600" . PHP_EOL .
+ "minimum_expiration = 60" . PHP_EOL .
+ "default_expiration = 120" . PHP_EOL .
+ "qualify_frequency = 60" . PHP_EOL .
+ "qualify_timeout = 3.0" . PHP_EOL . PHP_EOL .
+
+ "[".$trunk['id']."]" . PHP_EOL .
+ "type = endpoint" . PHP_EOL .
+ "100rel = no" . PHP_EOL .
+ "context = ".$trunk['id']."-incoming" . PHP_EOL .
+ "dtmf_mode = auto" . PHP_EOL .
+ "disallow = all" . PHP_EOL .
+ "allow = opus" . PHP_EOL .
+ "allow = alaw" . PHP_EOL .
+ "allow = h264" . PHP_EOL .
+ "rtp_symmetric = yes" . PHP_EOL .
+ "force_rport = yes" . PHP_EOL .
+ "rewrite_contact = yes" . PHP_EOL .
+ "ice_support = no" . PHP_EOL .
+ "direct_media = no" . PHP_EOL .
+ "contact_user = ".$trunk['id']. PHP_EOL .
+ "sdp_session = mikopbx" . PHP_EOL .
+ "language = ru-ru" . PHP_EOL .
+ "aors = ".$trunk['id'] . PHP_EOL .
+ "timers = no" . PHP_EOL .
+ "rtp_timeout = 30" . PHP_EOL .
+ "rtp_timeout_hold = 30" . PHP_EOL .
+ "auth = ".$trunk['id']."-AUTH" . PHP_EOL .
+ "inband_progress = yes" . PHP_EOL .
+ "tone_zone = ru" . PHP_EOL;
+ }elseif (intval($trunk['providerType']) === TrunksFMC::PROVIDER_TYPE_MCN) {
+ $config .= "[".$trunk['id']."]" . PHP_EOL;
+ $config .= 'type = identify' . PHP_EOL;
+ $config .= 'endpoint = '.$trunk['id'] . PHP_EOL;
+ // $config .= 'match = 127.0.0.1'. PHP_EOL;
+ $config .= 'match = 127.0.0.1:'.$settings->sipPort . PHP_EOL;
+ $config .= PHP_EOL;
+
+ $config .= "[".$trunk['id']."]" . PHP_EOL;
+ $config .= 'type = aor' . PHP_EOL;
+ $config .= 'max_contacts = 1' . PHP_EOL;
+ $config .= 'maximum_expiration = 3600' . PHP_EOL;
+ $config .= 'minimum_expiration = 60' . PHP_EOL;
+ $config .= 'default_expiration = 120' . PHP_EOL;
+ $config .= 'contact = sip:127.0.0.1:'.$settings->sipPort . PHP_EOL;
+ $config .= 'qualify_frequency = 60' . PHP_EOL;
+ $config .= 'qualify_timeout = 3.0' . PHP_EOL;
+ $config .= PHP_EOL;
+
+ $config .= "[".$trunk['id']."]" . PHP_EOL;
+ $config .= 'type = endpoint' . PHP_EOL;
+ $config .= '100rel = no' . PHP_EOL;
+ $config .= "context = ".$trunk['id']."-incoming" . PHP_EOL;
+ $config .= 'dtmf_mode = auto' . PHP_EOL;
+ $config .= 'disallow = all' . PHP_EOL;
+ $config .= 'allow = alaw' . PHP_EOL;
+ $config .= 'rtp_symmetric = yes' . PHP_EOL;
+ $config .= 'force_rport = yes' . PHP_EOL;
+ $config .= 'rewrite_contact = yes' . PHP_EOL;
+ $config .= 'ice_support = no' . PHP_EOL;
+ $config .= 'direct_media = no' . PHP_EOL;
+ $config .= 'from_user = ; username' . PHP_EOL;
+ $config .= 'from_domain = 127.0.0.1' . PHP_EOL;
+ $config .= 'contact_user = ; username' . PHP_EOL;
+ $config .= 'sdp_session = mikopbx' . PHP_EOL;
+ $config .= 'language = ru-ru' . PHP_EOL;
+ $config .= "aors = ".$trunk['id'] . PHP_EOL;
+ $config .= 'timers = no' . PHP_EOL;
+ $config .= 'rtp_timeout = 30' . PHP_EOL;
+ $config .= 'rtp_timeout_hold = 30' . PHP_EOL;
+ $config .= 'inband_progress = yes' . PHP_EOL;
+ $config .= 'tone_zone = ru' . PHP_EOL;
+ }
+ }
+ return $config;
+ }
+
+ public function onAfterModuleEnable(): void
+ {
+ parent::onAfterModuleEnable();
+ $scriptPath = $this->moduleDir.'/bin/init-asterisk.php';
+ $phpPath = Util::which('php');
+ shell_exec("$phpPath -f $scriptPath start");
+ PBX::sipReload();
+ PBX::dialplanReload();
+ }
+
+ public function onAfterModuleDisable(): void
+ {
+ parent::onAfterModuleDisable();
+ $scriptPath = $this->moduleDir.'/bin/init-asterisk.php';
+ $phpPath = Util::which('php');
+ shell_exec("$phpPath -f $scriptPath stop");
+ PBX::sipReload();
+ PBX::dialplanReload();
+ }
+
+ public static function getLogDir():string
+ {
+ return Directories::getDir(Directories::CORE_LOGS_DIR) . '/ModuleConnectorFMC';
+ }
+
+ public static function getCallDir():string
+ {
+ return dirname(__DIR__).'/db/CALL_DATA';
+ }
+
+ public static function getB24UsersDir():string
+ {
+ return dirname(__DIR__).'/db/B24_USERS';
+ }
+
+ public static function getAmiPort():string
+ {
+ return ModuleConnectorFMC::getValueByKey('amiPort');
+ }
+
+ /**
+ * Rotates the specified PBX log file.
+ * @param string $fileName The name of the log file to rotate.
+ */
+ public static function rotatePbxLog(string $fileName = 'full'): void
+ {
+ $di = MikoPBXVersion::getDefaultDi();
+ $asteriskPath = Util::which('asterisk');
+ if ($di === null) {
+ return;
+ }
+ $max_size = 10;
+ $log_dir = self::getLogDir();
+ $text_config = "$log_dir$fileName {
+ nocreate
+ nocopytruncate
+ delaycompress
+ nomissingok
+ start 0
+ rotate 3
+ size {$max_size}M
+ missingok
+ noolddir
+ postrotate
+ $asteriskPath -rx 'logger reload' > /dev/null 2> /dev/null
+ endscript
+}";
+ $varEtcDir = $di->getShared('config')->path('core.varEtcDir');
+ $path_conf = $varEtcDir . '/ModuleConnectorFMC_' . $fileName . '.conf';
+ file_put_contents($path_conf, $text_config);
+ $mb10 = $max_size * 1024 * 1024;
+
+ $options = '';
+ if (Util::mFileSize("$log_dir/$fileName") > $mb10) {
+ $options = '-f';
+ }
+ $logrotatePath = Util::which('logrotate');
+ Processes::mwExecBg("$logrotatePath $options '$path_conf' > /dev/null 2> /dev/null");
+ }
+
+ /**
+ * @param array $tasks
+ */
+ public function createCronTasks(array &$tasks): void
+ {
+ $phpPath = Util::which('php');
+ $tasks[] = "0 1 * * * $phpPath $this->moduleDir/bin/rotate-logs.php > /dev/null 2>&1".PHP_EOL;
+ $tasks[] = "*/1 * * * * $phpPath $this->moduleDir/bin/init-asterisk.php > /dev/null 2>&1".PHP_EOL;
+ }
+
+ /**
+ * REST API модуля.
+ * @return array[]
+ */
+ public function getPBXCoreRESTAdditionalRoutes(): array
+ {
+ return [
+ [ApiController::class, 'onCallAnswer','/pbxcore/api/module-connector-fmc/on-call-answer', 'post', '/', true],
+ ];
+ }
+}
\ No newline at end of file
diff --git a/Lib/Manager.php b/Lib/Manager.php
new file mode 100644
index 0000000..e59d570
--- /dev/null
+++ b/Lib/Manager.php
@@ -0,0 +1,135 @@
+.
+ */
+
+namespace Modules\ModuleConnectorFMC\Lib;
+use MikoPBX\Core\System\SystemMessages;
+use MikoPBX\Core\Asterisk\AsteriskManager;
+
+class Manager extends AsteriskManager
+{
+
+ /**
+ * Connect to Asterisk
+ *
+ * @param ?string $server
+ * @param ?string $username
+ * @param ?string $secret
+ * @param string $events
+ *
+ * @return bool true on success
+ * @example examples/sip_show_peer.php Get information about a sip peer
+ *
+ */
+ public function connect($server = null, $username = null, $secret = null, $events = 'on'): bool
+ {
+ $this->listenEvents = $events;
+ // use config if not specified
+ if (is_null($server)) {
+ $server = $this->config['asmanager']['server'];
+ }
+ if (is_null($username)) {
+ $username = $this->config['asmanager']['username'];
+ }
+ if (is_null($secret)) {
+ $secret = $this->config['asmanager']['secret'];
+ }
+
+ // get port from server if specified
+ if (strpos($server, ':') !== false) {
+ $c = explode(':', $server);
+ $this->server = $c[0];
+ $this->port = (int)$c[1];
+ } else {
+ $this->server = $server;
+ $this->port = $this->config['asmanager']['port'];
+ }
+
+ // connect the socket
+ $errno = $errStr = null;
+ $timeout = 2;
+ try {
+ $this->socket = fsockopen($this->server, $this->port, $errno, $errStr, $timeout);
+ }catch (\Throwable $e){
+ SystemMessages::sysLogMsg('AMI', "Exceptions, Unable to connect to manager $server ($errno): $errStr", LOG_ERR);
+ return false;
+ }
+ if ($this->socket === false) {
+ SystemMessages::sysLogMsg('AMI', "Unable to connect to manager $server ($errno): $errStr", LOG_ERR);
+ return false;
+ }
+ stream_set_timeout($this->socket, 1, 0);
+
+ // read the header
+ $str = $this->getStringDataFromSocket();
+ if ($str === '') {
+ // a problem.
+ SystemMessages::sysLogMsg('AMI', "Asterisk Manager header not received.", LOG_ERR);
+ return false;
+ }
+
+ // login
+ $res = $this->sendRequest('login', ['Username' => $username, 'Secret' => $secret, 'Events' => $events]);
+ if ($res['Response'] !== 'Success') {
+ $this->_loggedIn = false;
+ SystemMessages::sysLogMsg('AMI', "Failed to login.", LOG_ERR);
+ $this->disconnect();
+ return false;
+ }
+ $this->_loggedIn = true;
+
+ return true;
+ }
+
+ /**
+ * Get string data from the socket response.
+ *
+ * @return string The string data from the socket response.
+ */
+ private function getStringDataFromSocket() {
+ $response = $this->getDataFromSocket();
+ return $response['data'] ?? '';
+ }
+
+ /**
+ * Get data from the socket.
+ *
+ * @return array An array containing the data from the socket response or an error message.
+ */
+ private function getDataFromSocket() {
+ $response = [];
+ if(!is_resource($this->socket)){
+ $response['error'] = 'Socket not init.';
+ return $response;
+ }
+ try {
+ $resultFgets = fgets($this->socket, 4096);
+ if($resultFgets !== false){
+ $buffer = trim($resultFgets);
+ $response['data'] = $buffer;
+ }else{
+ $response['error'] = 'Read data error.';
+ }
+
+ }catch (\Throwable $e){
+ $response['error'] = $e->getMessage();
+ }
+
+ return $response;
+ }
+}
\ No newline at end of file
diff --git a/Lib/MikoPBXVersion.php b/Lib/MikoPBXVersion.php
new file mode 100644
index 0000000..67e57d6
--- /dev/null
+++ b/Lib/MikoPBXVersion.php
@@ -0,0 +1,102 @@
+.
+ */
+namespace Modules\ModuleConnectorFMC\Lib;
+
+use MikoPBX\Common\Models\PbxSettings;
+
+class MikoPBXVersion
+{
+ /**
+ * Return true if current version of PBX based on Phalcon 5+
+ * @return bool
+ */
+ public static function isPhalcon5Version(): bool
+ {
+ $pbxVersion = PbxSettings::getValueByKey('PBXVersion');
+ return version_compare($pbxVersion, '2024.2.3', '>');
+ }
+
+ /**
+ * Return Di interface for the current version of PBX
+ * @return \Phalcon\Di\DiInterface|null
+ */
+ public static function getDefaultDi()
+ {
+ if (self::isPhalcon5Version()) {
+ return \Phalcon\Di\Di::getDefault();
+ } else {
+ return \Phalcon\Di::getDefault();
+ }
+ }
+
+ /**
+ * Return Validation class for the current version of PBX
+ * @return class-string<\Phalcon\Filter\Validation>|class-string<\Phalcon\Validation>
+ */
+ public static function getValidationClass(): string
+ {
+ if (self::isPhalcon5Version()) {
+ return \Phalcon\Filter\Validation::class;
+ } else {
+ return \Phalcon\Validation::class;
+ }
+ }
+
+ /**
+ * Return Uniqueness class for the current version of PBX
+ * @return class-string<\Phalcon\Filter\Validation\Validator\Uniqueness>|class-string<\Phalcon\Validation\Validator\Uniqueness>
+ */
+ public static function getUniquenessClass(): string
+ {
+ if (self::isPhalcon5Version()) {
+ return \Phalcon\Filter\Validation\Validator\Uniqueness::class;
+ } else {
+ return \Phalcon\Validation\Validator\Uniqueness::class;
+ }
+ }
+
+ /**
+ * Return Text class for the current version of PBX
+ *
+ * @return class-string<\MikoPBX\Common\Library\Text>|class-string<\Phalcon\Text>
+ */
+ public static function getTextClass(): string
+ {
+ if (self::isPhalcon5Version()) {
+ return \MikoPBX\Common\Library\Text::class;
+ } else {
+ return \Phalcon\Text::class;
+ }
+ }
+
+ /**
+ * Return Logger class for the current version of PBX
+ *
+ * @return class-string<\Phalcon\Logger\Logger>|class-string<\Phalcon\Logger>
+ */
+ public static function getLoggerClass(): string
+ {
+ if (self::isPhalcon5Version()) {
+ return \Phalcon\Logger\Logger::class;
+ } else {
+ return \Phalcon\Logger::class;
+ }
+ }
+}
diff --git a/Lib/RestAPI/Controllers/ApiController.php b/Lib/RestAPI/Controllers/ApiController.php
new file mode 100644
index 0000000..049c4fe
--- /dev/null
+++ b/Lib/RestAPI/Controllers/ApiController.php
@@ -0,0 +1,53 @@
+request->getPost();
+ SystemMessages::sysLogMsg("B24_REST", json_encode($_SERVER['REMOTE_ADDR']));
+ if($postData['event'] === 'ONVOXIMPLANTCALLSTART'){
+ $cid = $postData['data']['CALL_ID']??'';
+ $callDataFile = ConnectorFMCConf::getCallDir()."/$cid";
+ if(file_exists($callDataFile)){
+ $call = json_decode(file_get_contents($callDataFile), true);
+ if(!empty($call['SRC_CHAN']) && !empty($call['DST_CHAN'])){
+ $am = new Manager();
+ $am->connect('127.0.0.1:'.ConnectorFMCConf::getAmiPort());
+ $commandParams = [
+ 'Channel' => $call['SRC_CHAN'],
+ 'ExtraChannel' => $call['DST_CHAN'],
+ 'Exten' => 's',
+ 'Context' => "orig-src-bridge",
+ 'Priority' => '1',
+ 'ExtraExten' => 'h',
+ 'ExtraContext' => "orig-src-bridge",
+ 'ExtraPriority'=> '1',
+ ];
+ $am->sendRequest('Redirect',$commandParams);
+ }
+ unlink($call);
+ }
+ }
+ $this->response->sendRaw();
+ }
+}
\ No newline at end of file
diff --git a/Messages/en.php b/Messages/en.php
new file mode 100644
index 0000000..34d0d62
--- /dev/null
+++ b/Messages/en.php
@@ -0,0 +1,27 @@
+ 'Module template - %repesent%',
+ 'mo_ModuleModuleConnectorFMC' => 'Module template',
+ 'BreadcrumbModuleConnectorFMC'=> 'Template module',
+ 'SubHeaderModuleConnectorFMC' => 'Example to create own modules',
+ 'module_template_AddNewRecord' => 'Add new',
+ 'module_connectorfmcTextFieldLabel' => 'Text field example',
+ 'module_connectorfmcTextAreaFieldLabel' => 'TextArea field example',
+ 'module_connectorfmcPasswordFieldLabel' => 'Password field example',
+ 'module_connectorfmcIntegerFieldLabel' => 'Integer field example',
+ 'module_connectorfmcCheckBoxFieldLabel' => 'CheckBox',
+ 'module_connectorfmcToggleFieldLabel' => 'Toggle',
+ 'module_connectorfmcDropDownFieldLabel' => 'Dropdown menu',
+ 'module_connectorfmcValidateValueIsEmpty' => 'Check the field, it looks like empty',
+ 'module_connectorfmcConnected' => 'Module connected',
+ 'module_connectorfmcDisconnected' => 'Module disconnected',
+ 'module_connectorfmcUpdateStatus' => 'Update module status',
+];
\ No newline at end of file
diff --git a/Messages/ru.php b/Messages/ru.php
new file mode 100644
index 0000000..1fae935
--- /dev/null
+++ b/Messages/ru.php
@@ -0,0 +1,45 @@
+ 'FMC коннектор - %repesent%',
+ 'mo_ModuleModuleConnectorFMC' => 'FMC коннектор',
+ 'BreadcrumbModuleConnectorFMC' => 'Подключение FMС устройств',
+ 'SubHeaderModuleConnectorFMC' => 'Подключение к АТС устройств, подключенных к другой АТС',
+ 'module_connectorfmc_userAgent' => 'User Agent',
+ 'module_connectorfmc_sipPort' => 'Порт SIP',
+ 'module_connectorfmc_outputEndpoint' => 'Логин',
+ 'module_connectorfmc_outputEndpointSecret' => 'Пароль',
+ 'module_connectorfmc_incomingEndpointHost' => 'Адрес сервера',
+ 'module_connectorfmc_incomingEndpointPort' => 'Порт',
+ 'module_connectorfmc_incomingEndpointLogin' => 'Логин',
+ 'module_connectorfmc_extensions' => 'Сотрудники',
+ 'module_connectorfmc_incomingEndpointSecret' => 'Пароль',
+ 'module_connectorfmc_amiPort' => 'Порт AMI',
+ 'module_connectorfmc_rtpPorts' => 'Диапазон RTP портов',
+ 'module_connectorfmc_rtpPortStart' => 'Начало RTP диапазона портов',
+ 'module_connectorfmc_rtpPortEnd' => 'Конец RTP диапазона портов',
+ 'module_connectorfmc_ConnectionSettings' => 'Настройки подключения',
+ 'module_connectorfmc_FmcServerSettings' => 'Настройки дополнительного локального SIP сервера',
+ 'module_connectorfmc_TitleIncomingFMC' => 'Входящие звонки на FMC',
+ 'module_connectorfmc_TitleOutgoingFMC' => 'Исходящие звонки с FMC',
+ 'module_connectorfmc_TitleOutgoingMessage' => 'При вызове с FMC устройства по SIP, MikoPBX запросит эти логин и пароль',
+ 'module_connectorfmc_useDelayedResponse' => 'Это SIP коннектор Bitrix24',
+ 'module_connectorfmc_incomingEndpointPortIsEmpty' => 'Порт не должен быть пустым.',
+ 'module_connectorfmc_incomingEndpointLoginIsEmpty' => 'Логин не должен быть пустым.',
+ 'module_connectorfmc_incomingEndpointSecretIsEmpty' => 'Пароль не должен быть пустым.',
+ 'module_connectorfmc_incomingEndpointHostIsEmpty' => 'Адрес сервера не должен быть пустым.',
+ 'module_connectorfmc_rtpPortStartIsEmpty' => 'Начальный порт RTP должен быть в диапазоне 40000..65000.',
+ 'module_connectorfmc_rtpPortEndIsEmpty' => 'Конечный порт RTP должен быть в диапазоне 40000..65000.',
+ 'module_connectorfmc_sipPortIsEmpty' => 'Порт SIP должен быть в диапазоне 5160..65000.',
+ 'module_connectorfmc_amiPortIsEmpty' => 'Порт AMI должен быть в диапазоне 55000..60000.',
+ 'module_connectorfmc_PROVIDER_TYPE_B24' => 'Bitrix24 SIP connector',
+ 'module_connectorfmc_PROVIDER_TYPE_MCN' => 'MCN',
+ 'module_connectorfmc_providerType' => 'Провайдер FMC',
+];
\ No newline at end of file
diff --git a/Models/ModuleConnectorFMC.php b/Models/ModuleConnectorFMC.php
new file mode 100644
index 0000000..908697a
--- /dev/null
+++ b/Models/ModuleConnectorFMC.php
@@ -0,0 +1,81 @@
+setSource('m_ModuleConnectorFMC');
+ parent::initialize();
+ }
+
+ /**
+ * Returns default or saved value for key if it exists on DB
+ *
+ * @param $key string value key
+ *
+ * @return string
+ */
+ public static function getValueByKey(string $key): string
+ {
+ try {
+ $currentSettings = parent::findFirst()->toArray();
+ return trim($currentSettings[$key]??'');
+ } catch (\Throwable $e) {
+ CriticalErrorsHandler::handleException($e);
+ }
+
+ return '';
+ }
+}
\ No newline at end of file
diff --git a/Models/TrunksFMC.php b/Models/TrunksFMC.php
new file mode 100644
index 0000000..9769571
--- /dev/null
+++ b/Models/TrunksFMC.php
@@ -0,0 +1,81 @@
+setSource('m_TrunksFMC');
+ parent::initialize();
+ }
+}
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5356b28
--- /dev/null
+++ b/README.md
@@ -0,0 +1,11 @@
+# MikoPBX extension module template #
+
+*Read this in other languages: [English](README.md), [Русский](README.ru.md).*
+
+## Module description ##
+
+We are working on the module developing guide here [https://docs.mikopbx.com](https://docs.mikopbx.com/mikopbx-development/)
+
+
+### Questions ###
+You are welcome to our telegram channel for developers [@mikopbx_dev](https://t.me/joinchat/AAPn5xSqZIpQnNnCAa3bBw)
diff --git a/Setup/PbxExtensionSetup.php b/Setup/PbxExtensionSetup.php
new file mode 100644
index 0000000..36987c6
--- /dev/null
+++ b/Setup/PbxExtensionSetup.php
@@ -0,0 +1,84 @@
+createSettingsTableByModelsAnnotations();
+
+ if ($result) {
+ $result = $this->registerNewModule();
+ }
+
+ if ($result) {
+ $result = $this->addToSidebar();
+ }
+
+ return $result;
+ }
+
+ /**
+ * Create folders on PBX system and apply rights
+ *
+ * @return bool result of installation
+ */
+ public function installFiles(): bool
+ {
+ return parent::installFiles();
+ }
+
+ /**
+ * Unregister module on PbxExtensionModules,
+ * Makes data backup if $keepSettings is true
+ *
+ * Before delete module we can do some soft delete changes, f.e. change forwarding rules i.e.
+ *
+ * @param $keepSettings bool creates backup folder with module settings
+ *
+ * @return bool uninstall result
+ */
+ public function unInstallDB($keepSettings = false): bool
+ {
+ return parent::unInstallDB($keepSettings);
+ }
+
+}
\ No newline at end of file
diff --git a/agi-bin/hangup.php b/agi-bin/hangup.php
new file mode 100644
index 0000000..36e9921
--- /dev/null
+++ b/agi-bin/hangup.php
@@ -0,0 +1,41 @@
+#!/usr/bin/php
+.
+ */
+
+use MikoPBX\Core\Asterisk\AGI;
+use MikoPBX\Core\Asterisk\AsteriskManager;
+use Modules\ModuleConnectorFMC\Lib\ConnectorFMCConf;
+
+require_once 'Globals.php';
+$agi = new AGI();
+$data = [
+ 'SRC_CHAN' => $agi->get_variable('SRC_CHAN',true),
+ 'DST_CHAN' => $agi->get_variable('DST_CHAN',true),
+ 'id' => $agi->get_variable('CHANNEL(linkedid)',true),
+ 'SRC_CID' => $agi->get_variable('SRC_CID',true),
+ 'SRC_DST' => $agi->get_variable('SRC_DST',true),
+ 'TIME' => time()
+];
+unlink(ConnectorFMCConf::getCallDir()."/{$data['id']}");
+
+if($argc>1){
+ $am = new AsteriskManager();
+ $am->connect('127.0.0.1:'.ConnectorFMCConf::getAmiPort());
+ $am->Hangup($argv[1]);
+}
diff --git a/agi-bin/save-state.php b/agi-bin/save-state.php
new file mode 100644
index 0000000..bb1bc47
--- /dev/null
+++ b/agi-bin/save-state.php
@@ -0,0 +1,36 @@
+#!/usr/bin/php
+.
+ */
+
+use MikoPBX\Core\Asterisk\AGI;
+use Modules\ModuleConnectorFMC\Lib\ConnectorFMCConf;
+
+require_once 'Globals.php';
+$agi = new AGI();
+$data = [
+ 'SRC_CHAN' => $agi->get_variable('SRC_CHAN',true),
+ 'DST_CHAN' => $agi->get_variable('DST_CHAN',true),
+ 'id' => $agi->get_variable('CHANNEL(linkedid)',true),
+ 'SRC_CID' => $agi->get_variable('SRC_CID',true),
+ 'SRC_DST' => $agi->get_variable('SRC_DST',true),
+ 'VI_CALL_ID' => $agi->get_variable('VI_CALL_ID',true),
+ 'TIME' => time()
+];
+
+file_put_contents(ConnectorFMCConf::getCallDir()."/{$data['VI_CALL_ID']}", json_encode($data));
\ No newline at end of file
diff --git a/bin/init-asterisk.php b/bin/init-asterisk.php
new file mode 100644
index 0000000..6decc58
--- /dev/null
+++ b/bin/init-asterisk.php
@@ -0,0 +1,33 @@
+.
+ */
+
+use Modules\ModuleConnectorFMC\Lib\ConfigureAsterisk;
+use MikoPBX\Core\System\Processes;
+require_once 'Globals.php';
+
+$title = 'init-fmc-asterisk';
+$pid = Processes::getPidOfProcess($title);
+if(!empty($pid)){
+ echo "Process exista $pid";
+ exit(1);
+}
+cli_set_process_title('init-fmc-asterisk');
+
+$config = new ConfigureAsterisk();
+$config->makeConfig($argv[1]??'');
\ No newline at end of file
diff --git a/bin/rotate-logs.php b/bin/rotate-logs.php
new file mode 100644
index 0000000..d11ca10
--- /dev/null
+++ b/bin/rotate-logs.php
@@ -0,0 +1,23 @@
+.
+ */
+
+use Modules\ModuleConnectorFMC\Lib\ConnectorFMCConf;
+require_once 'Globals.php';
+
+ConnectorFMCConf::rotatePbxLog();
\ No newline at end of file
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..fe55b8f
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,60 @@
+{
+ "name": "mikopbx/moduletemplate",
+ "description": "ModuleConnectorFMC",
+ "require": {
+ "mikopbx/core": ">=2020.2.757"
+ },
+ "autoload": {
+ "psr-4": {
+ "Modules\\ModuleConnectorFMC\\": "/"
+ }
+ },
+ "type": "application",
+ "keywords": [
+ "pbx",
+ "asterisk",
+ "freepbx",
+ "mikopbx",
+ "sip",
+ "voip",
+ "phalcon",
+ "telephony",
+ "uc"
+ ],
+ "license": "GPL-3.0-or-later",
+ "homepage": "https://github.com/mikopbx/ModuleConnectorFMC",
+ "support": {
+ "email": "help@mikopbx.com",
+ "wiki": "https://wiki.mikopbx.com",
+ "issues": "https://github.com/mikopbx/Core/issues",
+ "source": "https://github.com/mikopbx/Core",
+ "forum": "https://qa.askozia.ru",
+ "chat": "https://t.me/mikopbx_dev"
+ },
+ "authors": [
+ {
+ "name": "Alexey Portnov",
+ "email": "apor@miko.ru",
+ "homepage": "https://www.facebook.com/PortnovAlexey",
+ "role": "Developer"
+ },
+ {
+ "name": "Nikolay Beketov",
+ "email": "nbek@miko.ru",
+ "homepage": "http://facebook.com/jorikfon",
+ "role": "Developer"
+ }
+ ],
+ "funding": [
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/mikopbx"
+ }
+ ],
+ "config": {
+ "sort-packages": true,
+ "platform": {
+ "php": "7.4.6"
+ }
+ }
+}
diff --git a/db/B24_USERS/folder4db b/db/B24_USERS/folder4db
new file mode 100644
index 0000000..e69de29
diff --git a/db/CALL_DATA/folder4db b/db/CALL_DATA/folder4db
new file mode 100644
index 0000000..e69de29
diff --git a/db/REST_RESULTS/folder4db b/db/REST_RESULTS/folder4db
new file mode 100644
index 0000000..e69de29
diff --git a/module.json b/module.json
new file mode 100644
index 0000000..ff03d31
--- /dev/null
+++ b/module.json
@@ -0,0 +1,14 @@
+{
+ "developer":"MIKO",
+ "moduleUniqueID":"ModuleConnectorFMC",
+ "support_email": "help@miko.ru",
+ "version": "%ModuleVersion%",
+ "min_pbx_version": "2024.1.114",
+ "release_settings": {
+ "publish_release": true,
+ "changelog_enabled": true,
+ "create_github_release": true
+ },
+ "lic_product_id": 198,
+ "lic_feature_id": 56
+}
\ No newline at end of file
diff --git a/public/assets/css/module-connectorfmc.css b/public/assets/css/module-connectorfmc.css
new file mode 100644
index 0000000..1fb349f
--- /dev/null
+++ b/public/assets/css/module-connectorfmc.css
@@ -0,0 +1,8 @@
+/*
+ * Copyright (C) MIKO LLC - All Rights Reserved
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ * Proprietary and confidential
+ * Written by Nikolay Beketov, 9 2018
+ *
+ */
+
diff --git a/public/assets/img/logo.svg b/public/assets/img/logo.svg
new file mode 100644
index 0000000..d4ec419
--- /dev/null
+++ b/public/assets/img/logo.svg
@@ -0,0 +1,113 @@
+
+
+
diff --git a/public/assets/js/module-connectorfmc-index.js b/public/assets/js/module-connectorfmc-index.js
new file mode 100644
index 0000000..3a16f52
--- /dev/null
+++ b/public/assets/js/module-connectorfmc-index.js
@@ -0,0 +1,156 @@
+"use strict";
+
+/*
+ * Copyright (C) MIKO LLC - All Rights Reserved
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ * Proprietary and confidential
+ * Written by Nikolay Beketov, 11 2018
+ *
+ */
+var idUrl = 'module-connector-f-m-c';
+var idForm = 'module-connectorfmc-form';
+var className = 'ModuleConnectorFMC';
+var inputClassName = 'mikopbx-module-input';
+/* global globalRootUrl, globalTranslate, Form, Config, $ */
+
+var ModuleConnectorFMC = {
+ $formObj: $('#' + idForm),
+ $checkBoxes: $('#' + idForm + ' .ui.checkbox'),
+ $dropDowns: $('#' + idForm + ' .ui.dropdown'),
+ $disabilityFields: $('#' + idForm + ' .disability'),
+
+ /**
+ * Field validation rules
+ * https://semantic-ui.com/behaviors/form.html
+ */
+ validateRules: {
+ incomingEndpointPort: {
+ identifier: 'incomingEndpointPort',
+ rules: [{
+ type: 'integer[1..65000]',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointPortIsEmpty
+ }]
+ },
+ incomingEndpointLogin: {
+ identifier: 'incomingEndpointLogin',
+ rules: [{
+ type: 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointLoginIsEmpty
+ }]
+ },
+ incomingEndpointSecret: {
+ identifier: 'incomingEndpointSecret',
+ rules: [{
+ type: 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointSecretIsEmpty
+ }]
+ },
+ incomingEndpointHost: {
+ identifier: 'incomingEndpointHost',
+ rules: [{
+ type: 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointHostIsEmpty
+ }]
+ },
+ sipPort: {
+ identifier: 'sipPort',
+ rules: [{
+ type: 'integer[5160..65000]',
+ prompt: globalTranslate.module_connectorfmc_sipPortIsEmpty
+ }]
+ },
+ amiPort: {
+ identifier: 'amiPort',
+ rules: [{
+ type: 'integer[55000..60000]',
+ prompt: globalTranslate.module_connectorfmc_amiPortIsEmpty
+ }]
+ },
+ rtpPortStart: {
+ identifier: 'rtpPortStart',
+ rules: [{
+ type: 'integer[40000..65000]',
+ prompt: globalTranslate.module_connectorfmc_rtpPortStartIsEmpty
+ }]
+ },
+ rtpPortEnd: {
+ identifier: 'rtpPortEnd',
+ rules: [{
+ type: 'integer[40000..65000]',
+ prompt: globalTranslate.module_connectorfmc_rtpPortEndIsEmpty
+ }]
+ }
+ },
+
+ /**
+ * On page load we init some Semantic UI library
+ */
+ initialize: function initialize() {
+ // инициализируем чекбоксы и выподающие менюшки
+ window[className].$checkBoxes.checkbox();
+ $('#useDelayedResponse').parent().checkbox();
+ window[className].$dropDowns.dropdown();
+ $('.menu .item').tab();
+ $(".accordion").accordion();
+ window[className].initializeForm();
+ $('#outputEndpoint').parent().find('i').on('click', function () {
+ $(this).parent().find('input').attr('value', 'SIP-FMC-' + window[className].generateUID(8).toUpperCase());
+ $(this).parent().find('input').trigger('change');
+ });
+ $('#outputEndpointSecret').parent().find('i').on('click', function () {
+ $(this).parent().find('input').val(window[className].generateUID(32));
+ $(this).parent().find('input').trigger('change');
+ });
+ },
+
+ /**
+ * Create new password
+ * @param length
+ * @returns {string}
+ */
+ generateUID: function generateUID(length) {
+ var charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ var password = "";
+
+ for (var i = 0; i < length; i++) {
+ var randomIndex = Math.floor(Math.random() * charset.length);
+ password += charset[randomIndex];
+ }
+
+ return password;
+ },
+
+ /**
+ * We can modify some data before form send
+ * @param settings
+ * @returns {*}
+ */
+ cbBeforeSendForm: function cbBeforeSendForm(settings) {
+ var result = settings;
+ result.data = window[className].$formObj.form('get values');
+ result.data.extensions = result.data.peers.join(',');
+ delete result.data.peers;
+ return result;
+ },
+
+ /**
+ * Some actions after forms send
+ */
+ cbAfterSendForm: function cbAfterSendForm() {},
+
+ /**
+ * Initialize form parameters
+ */
+ initializeForm: function initializeForm() {
+ Form.$formObj = window[className].$formObj;
+ Form.url = "".concat(globalRootUrl).concat(idUrl, "/save");
+ Form.validateRules = window[className].validateRules;
+ Form.cbBeforeSendForm = window[className].cbBeforeSendForm;
+ Form.cbAfterSendForm = window[className].cbAfterSendForm;
+ Form.initialize();
+ }
+};
+$(document).ready(function () {
+ window[className].initialize();
+});
+//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNyYy9tb2R1bGUtY29ubmVjdG9yZm1jLWluZGV4LmpzIl0sIm5hbWVzIjpbImlkVXJsIiwiaWRGb3JtIiwiY2xhc3NOYW1lIiwiaW5wdXRDbGFzc05hbWUiLCJNb2R1bGVDb25uZWN0b3JGTUMiLCIkZm9ybU9iaiIsIiQiLCIkY2hlY2tCb3hlcyIsIiRkcm9wRG93bnMiLCIkZGlzYWJpbGl0eUZpZWxkcyIsInZhbGlkYXRlUnVsZXMiLCJpbmNvbWluZ0VuZHBvaW50UG9ydCIsImlkZW50aWZpZXIiLCJydWxlcyIsInR5cGUiLCJwcm9tcHQiLCJnbG9iYWxUcmFuc2xhdGUiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRQb3J0SXNFbXB0eSIsImluY29taW5nRW5kcG9pbnRMb2dpbiIsIm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludExvZ2luSXNFbXB0eSIsImluY29taW5nRW5kcG9pbnRTZWNyZXQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRTZWNyZXRJc0VtcHR5IiwiaW5jb21pbmdFbmRwb2ludEhvc3QiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRIb3N0SXNFbXB0eSIsInNpcFBvcnQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX3NpcFBvcnRJc0VtcHR5IiwiYW1pUG9ydCIsIm1vZHVsZV9jb25uZWN0b3JmbWNfYW1pUG9ydElzRW1wdHkiLCJydHBQb3J0U3RhcnQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX3J0cFBvcnRTdGFydElzRW1wdHkiLCJydHBQb3J0RW5kIiwibW9kdWxlX2Nvbm5lY3RvcmZtY19ydHBQb3J0RW5kSXNFbXB0eSIsImluaXRpYWxpemUiLCJ3aW5kb3ciLCJjaGVja2JveCIsInBhcmVudCIsImRyb3Bkb3duIiwidGFiIiwiYWNjb3JkaW9uIiwiaW5pdGlhbGl6ZUZvcm0iLCJmaW5kIiwib24iLCJhdHRyIiwiZ2VuZXJhdGVVSUQiLCJ0b1VwcGVyQ2FzZSIsInRyaWdnZXIiLCJ2YWwiLCJsZW5ndGgiLCJjaGFyc2V0IiwicGFzc3dvcmQiLCJpIiwicmFuZG9tSW5kZXgiLCJNYXRoIiwiZmxvb3IiLCJyYW5kb20iLCJjYkJlZm9yZVNlbmRGb3JtIiwic2V0dGluZ3MiLCJyZXN1bHQiLCJkYXRhIiwiZm9ybSIsImV4dGVuc2lvbnMiLCJwZWVycyIsImpvaW4iLCJjYkFmdGVyU2VuZEZvcm0iLCJGb3JtIiwidXJsIiwiZ2xvYmFsUm9vdFVybCIsImRvY3VtZW50IiwicmVhZHkiXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFNQSxLQUFLLEdBQU8sd0JBQWxCO0FBQ0EsSUFBTUMsTUFBTSxHQUFNLDBCQUFsQjtBQUNBLElBQU1DLFNBQVMsR0FBRyxvQkFBbEI7QUFDQSxJQUFNQyxjQUFjLEdBQUcsc0JBQXZCO0FBRUE7O0FBQ0EsSUFBTUMsa0JBQWtCLEdBQUc7QUFDMUJDLEVBQUFBLFFBQVEsRUFBRUMsQ0FBQyxDQUFDLE1BQUlMLE1BQUwsQ0FEZTtBQUUxQk0sRUFBQUEsV0FBVyxFQUFFRCxDQUFDLENBQUMsTUFBSUwsTUFBSixHQUFXLGVBQVosQ0FGWTtBQUcxQk8sRUFBQUEsVUFBVSxFQUFFRixDQUFDLENBQUMsTUFBSUwsTUFBSixHQUFXLGVBQVosQ0FIYTtBQUkxQlEsRUFBQUEsaUJBQWlCLEVBQUVILENBQUMsQ0FBQyxNQUFJTCxNQUFKLEdBQVcsZUFBWixDQUpNOztBQU0xQjtBQUNEO0FBQ0E7QUFDQTtBQUNDUyxFQUFBQSxhQUFhLEVBQUU7QUFDZEMsSUFBQUEsb0JBQW9CLEVBQUU7QUFDckJDLE1BQUFBLFVBQVUsRUFBRSxzQkFEUztBQUVyQkMsTUFBQUEsS0FBSyxFQUFFLENBQ047QUFDQ0MsUUFBQUEsSUFBSSxFQUFFLG1CQURQO0FBRUNDLFFBQUFBLE1BQU0sRUFBRUMsZUFBZSxDQUFDQztBQUZ6QixPQURNO0FBRmMsS0FEUjtBQVVkQyxJQUFBQSxxQkFBcUIsRUFBRTtBQUN0Qk4sTUFBQUEsVUFBVSxFQUFFLHVCQURVO0FBRXRCQyxNQUFBQSxLQUFLLEVBQUUsQ0FDTjtBQUNDQyxRQUFBQSxJQUFJLEVBQUssT0FEVjtBQUVDQyxRQUFBQSxNQUFNLEVBQUVDLGVBQWUsQ0FBQ0c7QUFGekIsT0FETTtBQUZlLEtBVlQ7QUFtQmRDLElBQUFBLHNCQUFzQixFQUFFO0FBQ3ZCUixNQUFBQSxVQUFVLEVBQUUsd0JBRFc7QUFFdkJDLE1BQUFBLEtBQUssRUFBRSxDQUNOO0FBQ0NDLFFBQUFBLElBQUksRUFBSyxPQURWO0FBRUNDLFFBQUFBLE1BQU0sRUFBRUMsZUFBZSxDQUFDSztBQUZ6QixPQURNO0FBRmdCLEtBbkJWO0FBNEJkQyxJQUFBQSxvQkFBb0IsRUFBRTtBQUNyQlYsTUFBQUEsVUFBVSxFQUFFLHNCQURTO0FBRXJCQyxNQUFBQSxLQUFLLEVBQUUsQ0FDTjtBQUNDQyxRQUFBQSxJQUFJLEVBQUssT0FEVjtBQUVDQyxRQUFBQSxNQUFNLEVBQUVDLGVBQWUsQ0FBQ087QUFGekIsT0FETTtBQUZjLEtBNUJSO0FBcUNkQyxJQUFBQSxPQUFPLEVBQUU7QUFDUlosTUFBQUEsVUFBVSxFQUFFLFNBREo7QUFFUkMsTUFBQUEsS0FBSyxFQUFFLENBQ047QUFDQ0MsUUFBQUEsSUFBSSxFQUFLLHNCQURWO0FBRUNDLFFBQUFBLE1BQU0sRUFBRUMsZUFBZSxDQUFDUztBQUZ6QixPQURNO0FBRkMsS0FyQ0s7QUE4Q2RDLElBQUFBLE9BQU8sRUFBRTtBQUNSZCxNQUFBQSxVQUFVLEVBQUUsU0FESjtBQUVSQyxNQUFBQSxLQUFLLEVBQUUsQ0FDTjtBQUNDQyxRQUFBQSxJQUFJLEVBQUssdUJBRFY7QUFFQ0MsUUFBQUEsTUFBTSxFQUFFQyxlQUFlLENBQUNXO0FBRnpCLE9BRE07QUFGQyxLQTlDSztBQXVEZEMsSUFBQUEsWUFBWSxFQUFFO0FBQ2JoQixNQUFBQSxVQUFVLEVBQUUsY0FEQztBQUViQyxNQUFBQSxLQUFLLEVBQUUsQ0FDTjtBQUNDQyxRQUFBQSxJQUFJLEVBQUssdUJBRFY7QUFFQ0MsUUFBQUEsTUFBTSxFQUFFQyxlQUFlLENBQUNhO0FBRnpCLE9BRE07QUFGTSxLQXZEQTtBQWdFZEMsSUFBQUEsVUFBVSxFQUFFO0FBQ1hsQixNQUFBQSxVQUFVLEVBQUUsWUFERDtBQUVYQyxNQUFBQSxLQUFLLEVBQUUsQ0FDTjtBQUNDQyxRQUFBQSxJQUFJLEVBQUssdUJBRFY7QUFFQ0MsUUFBQUEsTUFBTSxFQUFFQyxlQUFlLENBQUNlO0FBRnpCLE9BRE07QUFGSTtBQWhFRSxHQVZXOztBQW9GMUI7QUFDRDtBQUNBO0FBQ0NDLEVBQUFBLFVBdkYwQix3QkF1RmI7QUFDWjtBQUNBQyxJQUFBQSxNQUFNLENBQUMvQixTQUFELENBQU4sQ0FBa0JLLFdBQWxCLENBQThCMkIsUUFBOUI7QUFFQTVCLElBQUFBLENBQUMsQ0FBQyxxQkFBRCxDQUFELENBQXlCNkIsTUFBekIsR0FBa0NELFFBQWxDO0FBRUFELElBQUFBLE1BQU0sQ0FBQy9CLFNBQUQsQ0FBTixDQUFrQk0sVUFBbEIsQ0FBNkI0QixRQUE3QjtBQUNBOUIsSUFBQUEsQ0FBQyxDQUFDLGFBQUQsQ0FBRCxDQUFpQitCLEdBQWpCO0FBQ0EvQixJQUFBQSxDQUFDLENBQUMsWUFBRCxDQUFELENBQWdCZ0MsU0FBaEI7QUFDQUwsSUFBQUEsTUFBTSxDQUFDL0IsU0FBRCxDQUFOLENBQWtCcUMsY0FBbEI7QUFFQWpDLElBQUFBLENBQUMsQ0FBQyxpQkFBRCxDQUFELENBQXFCNkIsTUFBckIsR0FBOEJLLElBQTlCLENBQW1DLEdBQW5DLEVBQXdDQyxFQUF4QyxDQUEyQyxPQUEzQyxFQUFvRCxZQUFXO0FBQzlEbkMsTUFBQUEsQ0FBQyxDQUFDLElBQUQsQ0FBRCxDQUFRNkIsTUFBUixHQUFpQkssSUFBakIsQ0FBc0IsT0FBdEIsRUFBK0JFLElBQS9CLENBQW9DLE9BQXBDLEVBQTZDLGFBQVdULE1BQU0sQ0FBQy9CLFNBQUQsQ0FBTixDQUFrQnlDLFdBQWxCLENBQThCLENBQTlCLEVBQWlDQyxXQUFqQyxFQUF4RDtBQUNBdEMsTUFBQUEsQ0FBQyxDQUFDLElBQUQsQ0FBRCxDQUFRNkIsTUFBUixHQUFpQkssSUFBakIsQ0FBc0IsT0FBdEIsRUFBK0JLLE9BQS9CLENBQXVDLFFBQXZDO0FBQ0EsS0FIRDtBQUlBdkMsSUFBQUEsQ0FBQyxDQUFDLHVCQUFELENBQUQsQ0FBMkI2QixNQUEzQixHQUFvQ0ssSUFBcEMsQ0FBeUMsR0FBekMsRUFBOENDLEVBQTlDLENBQWlELE9BQWpELEVBQTBELFlBQVc7QUFDcEVuQyxNQUFBQSxDQUFDLENBQUMsSUFBRCxDQUFELENBQVE2QixNQUFSLEdBQWlCSyxJQUFqQixDQUFzQixPQUF0QixFQUErQk0sR0FBL0IsQ0FBbUNiLE1BQU0sQ0FBQy9CLFNBQUQsQ0FBTixDQUFrQnlDLFdBQWxCLENBQThCLEVBQTlCLENBQW5DO0FBQ0FyQyxNQUFBQSxDQUFDLENBQUMsSUFBRCxDQUFELENBQVE2QixNQUFSLEdBQWlCSyxJQUFqQixDQUFzQixPQUF0QixFQUErQkssT0FBL0IsQ0FBdUMsUUFBdkM7QUFDQSxLQUhEO0FBSUEsR0ExR3lCOztBQTRHMUI7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNDRixFQUFBQSxXQWpIMEIsdUJBaUhkSSxNQWpIYyxFQWlITjtBQUNuQixRQUFNQyxPQUFPLEdBQUcsZ0VBQWhCO0FBQ0EsUUFBSUMsUUFBUSxHQUFHLEVBQWY7O0FBQ0EsU0FBSyxJQUFJQyxDQUFDLEdBQUcsQ0FBYixFQUFnQkEsQ0FBQyxHQUFHSCxNQUFwQixFQUE0QkcsQ0FBQyxFQUE3QixFQUFpQztBQUNoQyxVQUFNQyxXQUFXLEdBQUdDLElBQUksQ0FBQ0MsS0FBTCxDQUFXRCxJQUFJLENBQUNFLE1BQUwsS0FBZ0JOLE9BQU8sQ0FBQ0QsTUFBbkMsQ0FBcEI7QUFDQUUsTUFBQUEsUUFBUSxJQUFJRCxPQUFPLENBQUNHLFdBQUQsQ0FBbkI7QUFDQTs7QUFDRCxXQUFPRixRQUFQO0FBQ0EsR0F6SHlCOztBQTBIMUI7QUFDRDtBQUNBO0FBQ0E7QUFDQTtBQUNDTSxFQUFBQSxnQkEvSDBCLDRCQStIVEMsUUEvSFMsRUErSEM7QUFDMUIsUUFBTUMsTUFBTSxHQUFHRCxRQUFmO0FBQ0FDLElBQUFBLE1BQU0sQ0FBQ0MsSUFBUCxHQUFjekIsTUFBTSxDQUFDL0IsU0FBRCxDQUFOLENBQWtCRyxRQUFsQixDQUEyQnNELElBQTNCLENBQWdDLFlBQWhDLENBQWQ7QUFDQUYsSUFBQUEsTUFBTSxDQUFDQyxJQUFQLENBQVlFLFVBQVosR0FBeUJILE1BQU0sQ0FBQ0MsSUFBUCxDQUFZRyxLQUFaLENBQWtCQyxJQUFsQixDQUF1QixHQUF2QixDQUF6QjtBQUNBLFdBQU9MLE1BQU0sQ0FBQ0MsSUFBUCxDQUFZRyxLQUFuQjtBQUNBLFdBQU9KLE1BQVA7QUFDQSxHQXJJeUI7O0FBc0kxQjtBQUNEO0FBQ0E7QUFDQ00sRUFBQUEsZUF6STBCLDZCQXlJUixDQUNqQixDQTFJeUI7O0FBMkkxQjtBQUNEO0FBQ0E7QUFDQ3hCLEVBQUFBLGNBOUkwQiw0QkE4SVQ7QUFDaEJ5QixJQUFBQSxJQUFJLENBQUMzRCxRQUFMLEdBQWdCNEIsTUFBTSxDQUFDL0IsU0FBRCxDQUFOLENBQWtCRyxRQUFsQztBQUNBMkQsSUFBQUEsSUFBSSxDQUFDQyxHQUFMLGFBQWNDLGFBQWQsU0FBOEJsRSxLQUE5QjtBQUNBZ0UsSUFBQUEsSUFBSSxDQUFDdEQsYUFBTCxHQUFxQnVCLE1BQU0sQ0FBQy9CLFNBQUQsQ0FBTixDQUFrQlEsYUFBdkM7QUFDQXNELElBQUFBLElBQUksQ0FBQ1QsZ0JBQUwsR0FBd0J0QixNQUFNLENBQUMvQixTQUFELENBQU4sQ0FBa0JxRCxnQkFBMUM7QUFDQVMsSUFBQUEsSUFBSSxDQUFDRCxlQUFMLEdBQXVCOUIsTUFBTSxDQUFDL0IsU0FBRCxDQUFOLENBQWtCNkQsZUFBekM7QUFDQUMsSUFBQUEsSUFBSSxDQUFDaEMsVUFBTDtBQUNBO0FBckp5QixDQUEzQjtBQXdKQTFCLENBQUMsQ0FBQzZELFFBQUQsQ0FBRCxDQUFZQyxLQUFaLENBQWtCLFlBQU07QUFDdkJuQyxFQUFBQSxNQUFNLENBQUMvQixTQUFELENBQU4sQ0FBa0I4QixVQUFsQjtBQUNBLENBRkQiLCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogQ29weXJpZ2h0IChDKSBNSUtPIExMQyAtIEFsbCBSaWdodHMgUmVzZXJ2ZWRcbiAqIFVuYXV0aG9yaXplZCBjb3B5aW5nIG9mIHRoaXMgZmlsZSwgdmlhIGFueSBtZWRpdW0gaXMgc3RyaWN0bHkgcHJvaGliaXRlZFxuICogUHJvcHJpZXRhcnkgYW5kIGNvbmZpZGVudGlhbFxuICogV3JpdHRlbiBieSBOaWtvbGF5IEJla2V0b3YsIDExIDIwMThcbiAqXG4gKi9cbmNvbnN0IGlkVXJsICAgICA9ICdtb2R1bGUtY29ubmVjdG9yLWYtbS1jJztcbmNvbnN0IGlkRm9ybSAgICA9ICdtb2R1bGUtY29ubmVjdG9yZm1jLWZvcm0nO1xuY29uc3QgY2xhc3NOYW1lID0gJ01vZHVsZUNvbm5lY3RvckZNQyc7XG5jb25zdCBpbnB1dENsYXNzTmFtZSA9ICdtaWtvcGJ4LW1vZHVsZS1pbnB1dCc7XG5cbi8qIGdsb2JhbCBnbG9iYWxSb290VXJsLCBnbG9iYWxUcmFuc2xhdGUsIEZvcm0sIENvbmZpZywgJCAqL1xuY29uc3QgTW9kdWxlQ29ubmVjdG9yRk1DID0ge1xuXHQkZm9ybU9iajogJCgnIycraWRGb3JtKSxcblx0JGNoZWNrQm94ZXM6ICQoJyMnK2lkRm9ybSsnIC51aS5jaGVja2JveCcpLFxuXHQkZHJvcERvd25zOiAkKCcjJytpZEZvcm0rJyAudWkuZHJvcGRvd24nKSxcblx0JGRpc2FiaWxpdHlGaWVsZHM6ICQoJyMnK2lkRm9ybSsnICAuZGlzYWJpbGl0eScpLFxuXG5cdC8qKlxuXHQgKiBGaWVsZCB2YWxpZGF0aW9uIHJ1bGVzXG5cdCAqIGh0dHBzOi8vc2VtYW50aWMtdWkuY29tL2JlaGF2aW9ycy9mb3JtLmh0bWxcblx0ICovXG5cdHZhbGlkYXRlUnVsZXM6IHtcblx0XHRpbmNvbWluZ0VuZHBvaW50UG9ydDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRQb3J0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlOiAnaW50ZWdlclsxLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludFBvcnRJc0VtcHR5LFxuXHRcdFx0XHR9LFxuXHRcdFx0XSxcblx0XHR9LFxuXHRcdGluY29taW5nRW5kcG9pbnRMb2dpbjoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRMb2dpbicsXG5cdFx0XHRydWxlczogW1xuXHRcdFx0XHR7XG5cdFx0XHRcdFx0dHlwZSAgIDogJ2VtcHR5Jyxcblx0XHRcdFx0XHRwcm9tcHQ6IGdsb2JhbFRyYW5zbGF0ZS5tb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRMb2dpbklzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0aW5jb21pbmdFbmRwb2ludFNlY3JldDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRTZWNyZXQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdlbXB0eScsXG5cdFx0XHRcdFx0cHJvbXB0OiBnbG9iYWxUcmFuc2xhdGUubW9kdWxlX2Nvbm5lY3RvcmZtY19pbmNvbWluZ0VuZHBvaW50U2VjcmV0SXNFbXB0eSxcblx0XHRcdFx0fSxcblx0XHRcdF0sXG5cdFx0fSxcblx0XHRpbmNvbWluZ0VuZHBvaW50SG9zdDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRIb3N0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnZW1wdHknLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludEhvc3RJc0VtcHR5LFxuXHRcdFx0XHR9LFxuXHRcdFx0XSxcblx0XHR9LFxuXHRcdHNpcFBvcnQ6IHtcblx0XHRcdGlkZW50aWZpZXI6ICdzaXBQb3J0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnaW50ZWdlcls1MTYwLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfc2lwUG9ydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0YW1pUG9ydDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2FtaVBvcnQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdpbnRlZ2VyWzU1MDAwLi42MDAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfYW1pUG9ydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0cnRwUG9ydFN0YXJ0OiB7XG5cdFx0XHRpZGVudGlmaWVyOiAncnRwUG9ydFN0YXJ0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnaW50ZWdlcls0MDAwMC4uNjUwMDBdJyxcblx0XHRcdFx0XHRwcm9tcHQ6IGdsb2JhbFRyYW5zbGF0ZS5tb2R1bGVfY29ubmVjdG9yZm1jX3J0cFBvcnRTdGFydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0cnRwUG9ydEVuZDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ3J0cFBvcnRFbmQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdpbnRlZ2VyWzQwMDAwLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfcnRwUG9ydEVuZElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdH0sXG5cdC8qKlxuXHQgKiBPbiBwYWdlIGxvYWQgd2UgaW5pdCBzb21lIFNlbWFudGljIFVJIGxpYnJhcnlcblx0ICovXG5cdGluaXRpYWxpemUoKSB7XG5cdFx0Ly8g0LjQvdC40YbQuNCw0LvQuNC30LjRgNGD0LXQvCDRh9C10LrQsdC+0LrRgdGLINC4INCy0YvQv9C+0LTQsNGO0YnQuNC1INC80LXQvdGO0YjQutC4XG5cdFx0d2luZG93W2NsYXNzTmFtZV0uJGNoZWNrQm94ZXMuY2hlY2tib3goKTtcblxuXHRcdCQoJyN1c2VEZWxheWVkUmVzcG9uc2UnKS5wYXJlbnQoKS5jaGVja2JveCgpXG5cblx0XHR3aW5kb3dbY2xhc3NOYW1lXS4kZHJvcERvd25zLmRyb3Bkb3duKCk7XG5cdFx0JCgnLm1lbnUgLml0ZW0nKS50YWIoKTtcblx0XHQkKFwiLmFjY29yZGlvblwiKS5hY2NvcmRpb24oKTtcblx0XHR3aW5kb3dbY2xhc3NOYW1lXS5pbml0aWFsaXplRm9ybSgpO1xuXG5cdFx0JCgnI291dHB1dEVuZHBvaW50JykucGFyZW50KCkuZmluZCgnaScpLm9uKCdjbGljaycsIGZ1bmN0aW9uKCkge1xuXHRcdFx0JCh0aGlzKS5wYXJlbnQoKS5maW5kKCdpbnB1dCcpLmF0dHIoJ3ZhbHVlJywgJ1NJUC1GTUMtJyt3aW5kb3dbY2xhc3NOYW1lXS5nZW5lcmF0ZVVJRCg4KS50b1VwcGVyQ2FzZSgpKTtcblx0XHRcdCQodGhpcykucGFyZW50KCkuZmluZCgnaW5wdXQnKS50cmlnZ2VyKCdjaGFuZ2UnKTtcblx0XHR9KTtcblx0XHQkKCcjb3V0cHV0RW5kcG9pbnRTZWNyZXQnKS5wYXJlbnQoKS5maW5kKCdpJykub24oJ2NsaWNrJywgZnVuY3Rpb24oKSB7XG5cdFx0XHQkKHRoaXMpLnBhcmVudCgpLmZpbmQoJ2lucHV0JykudmFsKHdpbmRvd1tjbGFzc05hbWVdLmdlbmVyYXRlVUlEKDMyKSk7XG5cdFx0XHQkKHRoaXMpLnBhcmVudCgpLmZpbmQoJ2lucHV0JykudHJpZ2dlcignY2hhbmdlJyk7XG5cdFx0fSk7XG5cdH0sXG5cblx0LyoqXG5cdCAqIENyZWF0ZSBuZXcgcGFzc3dvcmRcblx0ICogQHBhcmFtIGxlbmd0aFxuXHQgKiBAcmV0dXJucyB7c3RyaW5nfVxuXHQgKi9cblx0Z2VuZXJhdGVVSUQobGVuZ3RoKSB7XG5cdFx0Y29uc3QgY2hhcnNldCA9IFwiYWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXpBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWjAxMjM0NTY3ODlcIjtcblx0XHRsZXQgcGFzc3dvcmQgPSBcIlwiO1xuXHRcdGZvciAobGV0IGkgPSAwOyBpIDwgbGVuZ3RoOyBpKyspIHtcblx0XHRcdGNvbnN0IHJhbmRvbUluZGV4ID0gTWF0aC5mbG9vcihNYXRoLnJhbmRvbSgpICogY2hhcnNldC5sZW5ndGgpO1xuXHRcdFx0cGFzc3dvcmQgKz0gY2hhcnNldFtyYW5kb21JbmRleF07XG5cdFx0fVxuXHRcdHJldHVybiBwYXNzd29yZDtcblx0fSxcblx0LyoqXG5cdCAqIFdlIGNhbiBtb2RpZnkgc29tZSBkYXRhIGJlZm9yZSBmb3JtIHNlbmRcblx0ICogQHBhcmFtIHNldHRpbmdzXG5cdCAqIEByZXR1cm5zIHsqfVxuXHQgKi9cblx0Y2JCZWZvcmVTZW5kRm9ybShzZXR0aW5ncykge1xuXHRcdGNvbnN0IHJlc3VsdCA9IHNldHRpbmdzO1xuXHRcdHJlc3VsdC5kYXRhID0gd2luZG93W2NsYXNzTmFtZV0uJGZvcm1PYmouZm9ybSgnZ2V0IHZhbHVlcycpO1xuXHRcdHJlc3VsdC5kYXRhLmV4dGVuc2lvbnMgPSByZXN1bHQuZGF0YS5wZWVycy5qb2luKCcsJyk7XG5cdFx0ZGVsZXRlIHJlc3VsdC5kYXRhLnBlZXJzO1xuXHRcdHJldHVybiByZXN1bHQ7XG5cdH0sXG5cdC8qKlxuXHQgKiBTb21lIGFjdGlvbnMgYWZ0ZXIgZm9ybXMgc2VuZFxuXHQgKi9cblx0Y2JBZnRlclNlbmRGb3JtKCkge1xuXHR9LFxuXHQvKipcblx0ICogSW5pdGlhbGl6ZSBmb3JtIHBhcmFtZXRlcnNcblx0ICovXG5cdGluaXRpYWxpemVGb3JtKCkge1xuXHRcdEZvcm0uJGZvcm1PYmogPSB3aW5kb3dbY2xhc3NOYW1lXS4kZm9ybU9iajtcblx0XHRGb3JtLnVybCA9IGAke2dsb2JhbFJvb3RVcmx9JHtpZFVybH0vc2F2ZWA7XG5cdFx0Rm9ybS52YWxpZGF0ZVJ1bGVzID0gd2luZG93W2NsYXNzTmFtZV0udmFsaWRhdGVSdWxlcztcblx0XHRGb3JtLmNiQmVmb3JlU2VuZEZvcm0gPSB3aW5kb3dbY2xhc3NOYW1lXS5jYkJlZm9yZVNlbmRGb3JtO1xuXHRcdEZvcm0uY2JBZnRlclNlbmRGb3JtID0gd2luZG93W2NsYXNzTmFtZV0uY2JBZnRlclNlbmRGb3JtO1xuXHRcdEZvcm0uaW5pdGlhbGl6ZSgpO1xuXHR9LFxufTtcblxuJChkb2N1bWVudCkucmVhZHkoKCkgPT4ge1xuXHR3aW5kb3dbY2xhc3NOYW1lXS5pbml0aWFsaXplKCk7XG59KTtcblxuIl19
\ No newline at end of file
diff --git a/public/assets/js/module-connectorfmc-index.js.map b/public/assets/js/module-connectorfmc-index.js.map
new file mode 100644
index 0000000..a5c11ce
--- /dev/null
+++ b/public/assets/js/module-connectorfmc-index.js.map
@@ -0,0 +1 @@
+{"version":3,"sources":["src/module-connectorfmc-index.js"],"names":["idUrl","idForm","className","inputClassName","ModuleConnectorFMC","$formObj","$","$checkBoxes","$dropDowns","$disabilityFields","validateRules","incomingEndpointPort","identifier","rules","type","prompt","globalTranslate","module_connectorfmc_incomingEndpointPortIsEmpty","incomingEndpointLogin","module_connectorfmc_incomingEndpointLoginIsEmpty","incomingEndpointSecret","module_connectorfmc_incomingEndpointSecretIsEmpty","incomingEndpointHost","module_connectorfmc_incomingEndpointHostIsEmpty","sipPort","module_connectorfmc_sipPortIsEmpty","amiPort","module_connectorfmc_amiPortIsEmpty","rtpPortStart","module_connectorfmc_rtpPortStartIsEmpty","rtpPortEnd","module_connectorfmc_rtpPortEndIsEmpty","initialize","window","checkbox","parent","dropdown","tab","accordion","initializeForm","find","on","attr","generateUID","toUpperCase","trigger","val","length","charset","password","i","randomIndex","Math","floor","random","cbBeforeSendForm","settings","result","data","form","extensions","peers","join","cbAfterSendForm","Form","url","globalRootUrl","document","ready"],"mappings":"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","sourcesContent":["/*\n * Copyright (C) MIKO LLC - All Rights Reserved\n * Unauthorized copying of this file, via any medium is strictly prohibited\n * Proprietary and confidential\n * Written by Nikolay Beketov, 11 2018\n *\n */\nconst idUrl = 'module-connector-f-m-c';\nconst idForm = 'module-connectorfmc-form';\nconst className = 'ModuleConnectorFMC';\nconst inputClassName = 'mikopbx-module-input';\n\n/* global globalRootUrl, globalTranslate, Form, Config, $ */\nconst ModuleConnectorFMC = {\n\t$formObj: $('#'+idForm),\n\t$checkBoxes: $('#'+idForm+' .ui.checkbox'),\n\t$dropDowns: $('#'+idForm+' .ui.dropdown'),\n\t$disabilityFields: $('#'+idForm+' .disability'),\n\n\t/**\n\t * Field validation rules\n\t * https://semantic-ui.com/behaviors/form.html\n\t */\n\tvalidateRules: {\n\t\tincomingEndpointPort: {\n\t\t\tidentifier: 'incomingEndpointPort',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype: 'integer[1..65000]',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_incomingEndpointPortIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tincomingEndpointLogin: {\n\t\t\tidentifier: 'incomingEndpointLogin',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'empty',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_incomingEndpointLoginIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tincomingEndpointSecret: {\n\t\t\tidentifier: 'incomingEndpointSecret',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'empty',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_incomingEndpointSecretIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tincomingEndpointHost: {\n\t\t\tidentifier: 'incomingEndpointHost',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'empty',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_incomingEndpointHostIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tsipPort: {\n\t\t\tidentifier: 'sipPort',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'integer[5160..65000]',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_sipPortIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\tamiPort: {\n\t\t\tidentifier: 'amiPort',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'integer[55000..60000]',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_amiPortIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\trtpPortStart: {\n\t\t\tidentifier: 'rtpPortStart',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'integer[40000..65000]',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_rtpPortStartIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t\trtpPortEnd: {\n\t\t\tidentifier: 'rtpPortEnd',\n\t\t\trules: [\n\t\t\t\t{\n\t\t\t\t\ttype : 'integer[40000..65000]',\n\t\t\t\t\tprompt: globalTranslate.module_connectorfmc_rtpPortEndIsEmpty,\n\t\t\t\t},\n\t\t\t],\n\t\t},\n\t},\n\t/**\n\t * On page load we init some Semantic UI library\n\t */\n\tinitialize() {\n\t\t// инициализируем чекбоксы и выподающие менюшки\n\t\twindow[className].$checkBoxes.checkbox();\n\n\t\t$('#useDelayedResponse').parent().checkbox()\n\n\t\twindow[className].$dropDowns.dropdown();\n\t\t$('.menu .item').tab();\n\t\t$(\".accordion\").accordion();\n\t\twindow[className].initializeForm();\n\n\t\t$('#outputEndpoint').parent().find('i').on('click', function() {\n\t\t\t$(this).parent().find('input').attr('value', 'SIP-FMC-'+window[className].generateUID(8).toUpperCase());\n\t\t\t$(this).parent().find('input').trigger('change');\n\t\t});\n\t\t$('#outputEndpointSecret').parent().find('i').on('click', function() {\n\t\t\t$(this).parent().find('input').val(window[className].generateUID(32));\n\t\t\t$(this).parent().find('input').trigger('change');\n\t\t});\n\t},\n\n\t/**\n\t * Create new password\n\t * @param length\n\t * @returns {string}\n\t */\n\tgenerateUID(length) {\n\t\tconst charset = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\t\tlet password = \"\";\n\t\tfor (let i = 0; i < length; i++) {\n\t\t\tconst randomIndex = Math.floor(Math.random() * charset.length);\n\t\t\tpassword += charset[randomIndex];\n\t\t}\n\t\treturn password;\n\t},\n\t/**\n\t * We can modify some data before form send\n\t * @param settings\n\t * @returns {*}\n\t */\n\tcbBeforeSendForm(settings) {\n\t\tconst result = settings;\n\t\tresult.data = window[className].$formObj.form('get values');\n\t\tresult.data.extensions = result.data.peers.join(',');\n\t\tdelete result.data.peers;\n\t\treturn result;\n\t},\n\t/**\n\t * Some actions after forms send\n\t */\n\tcbAfterSendForm() {\n\t},\n\t/**\n\t * Initialize form parameters\n\t */\n\tinitializeForm() {\n\t\tForm.$formObj = window[className].$formObj;\n\t\tForm.url = `${globalRootUrl}${idUrl}/save`;\n\t\tForm.validateRules = window[className].validateRules;\n\t\tForm.cbBeforeSendForm = window[className].cbBeforeSendForm;\n\t\tForm.cbAfterSendForm = window[className].cbAfterSendForm;\n\t\tForm.initialize();\n\t},\n};\n\n$(document).ready(() => {\n\twindow[className].initialize();\n});\n\n"],"file":"module-connectorfmc-index.js"}
\ No newline at end of file
diff --git a/public/assets/js/src/module-connectorfmc-index.js b/public/assets/js/src/module-connectorfmc-index.js
new file mode 100644
index 0000000..6b5b3dd
--- /dev/null
+++ b/public/assets/js/src/module-connectorfmc-index.js
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) MIKO LLC - All Rights Reserved
+ * Unauthorized copying of this file, via any medium is strictly prohibited
+ * Proprietary and confidential
+ * Written by Nikolay Beketov, 11 2018
+ *
+ */
+const idUrl = 'module-connector-f-m-c';
+const idForm = 'module-connectorfmc-form';
+const className = 'ModuleConnectorFMC';
+const inputClassName = 'mikopbx-module-input';
+
+/* global globalRootUrl, globalTranslate, Form, Config, $ */
+const ModuleConnectorFMC = {
+ $formObj: $('#'+idForm),
+ $checkBoxes: $('#'+idForm+' .ui.checkbox'),
+ $dropDowns: $('#'+idForm+' .ui.dropdown'),
+ $disabilityFields: $('#'+idForm+' .disability'),
+
+ /**
+ * Field validation rules
+ * https://semantic-ui.com/behaviors/form.html
+ */
+ validateRules: {
+ incomingEndpointPort: {
+ identifier: 'incomingEndpointPort',
+ rules: [
+ {
+ type: 'integer[1..65000]',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointPortIsEmpty,
+ },
+ ],
+ },
+ incomingEndpointLogin: {
+ identifier: 'incomingEndpointLogin',
+ rules: [
+ {
+ type : 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointLoginIsEmpty,
+ },
+ ],
+ },
+ incomingEndpointSecret: {
+ identifier: 'incomingEndpointSecret',
+ rules: [
+ {
+ type : 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointSecretIsEmpty,
+ },
+ ],
+ },
+ incomingEndpointHost: {
+ identifier: 'incomingEndpointHost',
+ rules: [
+ {
+ type : 'empty',
+ prompt: globalTranslate.module_connectorfmc_incomingEndpointHostIsEmpty,
+ },
+ ],
+ },
+ sipPort: {
+ identifier: 'sipPort',
+ rules: [
+ {
+ type : 'integer[5160..65000]',
+ prompt: globalTranslate.module_connectorfmc_sipPortIsEmpty,
+ },
+ ],
+ },
+ amiPort: {
+ identifier: 'amiPort',
+ rules: [
+ {
+ type : 'integer[55000..60000]',
+ prompt: globalTranslate.module_connectorfmc_amiPortIsEmpty,
+ },
+ ],
+ },
+ rtpPortStart: {
+ identifier: 'rtpPortStart',
+ rules: [
+ {
+ type : 'integer[40000..65000]',
+ prompt: globalTranslate.module_connectorfmc_rtpPortStartIsEmpty,
+ },
+ ],
+ },
+ rtpPortEnd: {
+ identifier: 'rtpPortEnd',
+ rules: [
+ {
+ type : 'integer[40000..65000]',
+ prompt: globalTranslate.module_connectorfmc_rtpPortEndIsEmpty,
+ },
+ ],
+ },
+ },
+ /**
+ * On page load we init some Semantic UI library
+ */
+ initialize() {
+ // инициализируем чекбоксы и выподающие менюшки
+ window[className].$checkBoxes.checkbox();
+
+ $('#useDelayedResponse').parent().checkbox()
+
+ window[className].$dropDowns.dropdown();
+ $('.menu .item').tab();
+ $(".accordion").accordion();
+ window[className].initializeForm();
+
+ $('#outputEndpoint').parent().find('i').on('click', function() {
+ $(this).parent().find('input').attr('value', 'SIP-FMC-'+window[className].generateUID(8).toUpperCase());
+ $(this).parent().find('input').trigger('change');
+ });
+ $('#outputEndpointSecret').parent().find('i').on('click', function() {
+ $(this).parent().find('input').val(window[className].generateUID(32));
+ $(this).parent().find('input').trigger('change');
+ });
+ },
+
+ /**
+ * Create new password
+ * @param length
+ * @returns {string}
+ */
+ generateUID(length) {
+ const charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+ let password = "";
+ for (let i = 0; i < length; i++) {
+ const randomIndex = Math.floor(Math.random() * charset.length);
+ password += charset[randomIndex];
+ }
+ return password;
+ },
+ /**
+ * We can modify some data before form send
+ * @param settings
+ * @returns {*}
+ */
+ cbBeforeSendForm(settings) {
+ const result = settings;
+ result.data = window[className].$formObj.form('get values');
+ result.data.extensions = result.data.peers.join(',');
+ delete result.data.peers;
+ return result;
+ },
+ /**
+ * Some actions after forms send
+ */
+ cbAfterSendForm() {
+ },
+ /**
+ * Initialize form parameters
+ */
+ initializeForm() {
+ Form.$formObj = window[className].$formObj;
+ Form.url = `${globalRootUrl}${idUrl}/save`;
+ Form.validateRules = window[className].validateRules;
+ Form.cbBeforeSendForm = window[className].cbBeforeSendForm;
+ Form.cbAfterSendForm = window[className].cbAfterSendForm;
+ Form.initialize();
+ },
+};
+
+$(document).ready(() => {
+ window[className].initialize();
+});
+
diff --git a/readme.ru.md b/readme.ru.md
new file mode 100644
index 0000000..4d09390
--- /dev/null
+++ b/readme.ru.md
@@ -0,0 +1,20 @@
+
+### Чтобы вызов прошел с номера sim карты, а не по основным маршрутам
+```
+[outgoing-custom];
+exten => _.X!,1,NoOp(-- ${PJSIP_HEADER(read,User-Agent)} --)
+ same => n,ExecIf($["${DIALPLAN_EXISTS(SIP-FMC-ENABLE-OUT,${CALLERID(num)},1)}" != "1"]?return)
+ ;same => n,ExecIf($["${PJSIP_HEADER(read,User-Agent)}" != "miko-b24-fmc" ]?return)
+ same => n,GosubIf($["${DIALPLAN_EXISTS(all-outgoing-SIP-FMC-Z5HGGORU-custom,${EXTEN},1)}" == "1"]?all-outgoing-SIP-FMC-Z5HGGORU-custom,${EXTEN},1)
+ same => n,return
+
+[SIP-FMC-ENABLE-OUT]
+exten => 201,1,NoOp(--- Out call ---)
+exten => 906,1,NoOp(--- Out call ---)
+exten => 908,1,NoOp(--- Out call ---)
+exten => 914,1,NoOp(--- Out call ---)
+exten => 903,1,NoOp(--- Out call ---)
+
+
+```
+ - "SIP-FMC-6AC9FJ0E" - идентификатор
\ No newline at end of file