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 @@ + +
+ {{ form.render('id') }} +
+
+
+

{{ t._('module_connectorfmc_TitleOutgoingFMC') }}

+
+
+ +
+ {{ form.render('outputEndpoint') }} + +
+
+
+ +
+ {{ form.render('outputEndpointSecret') }} + +
+
+
+

{{ t._('module_connectorfmc_TitleOutgoingMessage') }}

+
+
+
+

{{ t._('module_connectorfmc_TitleIncomingFMC') }}

+
+
+ + {{ form.render('incomingEndpointHost') }} +
+
+ + {{ form.render('incomingEndpointPort') }} +
+
+ + {{ form.render('incomingEndpointLogin') }} +
+
+ + {{ form.render('incomingEndpointSecret') }} +
+
+
+
+
+ + {{ form.render('extensions') }} + {{ form.render('peers') }} +
+
+ + {{ form.render('providerType') }} +
+
+
+
+ + {{ t._('module_connectorfmc_FmcServerSettings') }} +
+
+
+
+
+ +
+
+ {{ form.render('sipPort') }} +
+
+
+
+ +
+
+ {{ form.render('amiPort') }} +
+
+
+
+ +
+
+ {{ form.render('rtpPortStart') }} +
+
+ +
+
+ {{ form.render('rtpPortEnd') }} +
+
+
+
+
+
+ {{ partial("partials/submitbutton",['indexurl':'pbx-extension-modules/index/']) }} +
\ 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 @@ + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + 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,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNyYy9tb2R1bGUtY29ubmVjdG9yZm1jLWluZGV4LmpzIl0sIm5hbWVzIjpbImlkVXJsIiwiaWRGb3JtIiwiY2xhc3NOYW1lIiwiaW5wdXRDbGFzc05hbWUiLCJNb2R1bGVDb25uZWN0b3JGTUMiLCIkZm9ybU9iaiIsIiQiLCIkY2hlY2tCb3hlcyIsIiRkcm9wRG93bnMiLCIkZGlzYWJpbGl0eUZpZWxkcyIsInZhbGlkYXRlUnVsZXMiLCJpbmNvbWluZ0VuZHBvaW50UG9ydCIsImlkZW50aWZpZXIiLCJydWxlcyIsInR5cGUiLCJwcm9tcHQiLCJnbG9iYWxUcmFuc2xhdGUiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRQb3J0SXNFbXB0eSIsImluY29taW5nRW5kcG9pbnRMb2dpbiIsIm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludExvZ2luSXNFbXB0eSIsImluY29taW5nRW5kcG9pbnRTZWNyZXQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRTZWNyZXRJc0VtcHR5IiwiaW5jb21pbmdFbmRwb2ludEhvc3QiLCJtb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRIb3N0SXNFbXB0eSIsInNpcFBvcnQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX3NpcFBvcnRJc0VtcHR5IiwiYW1pUG9ydCIsIm1vZHVsZV9jb25uZWN0b3JmbWNfYW1pUG9ydElzRW1wdHkiLCJydHBQb3J0U3RhcnQiLCJtb2R1bGVfY29ubmVjdG9yZm1jX3J0cFBvcnRTdGFydElzRW1wdHkiLCJydHBQb3J0RW5kIiwibW9kdWxlX2Nvbm5lY3RvcmZtY19ydHBQb3J0RW5kSXNFbXB0eSIsImluaXRpYWxpemUiLCJ3aW5kb3ciLCJjaGVja2JveCIsInBhcmVudCIsImRyb3Bkb3duIiwidGFiIiwiYWNjb3JkaW9uIiwiaW5pdGlhbGl6ZUZvcm0iLCJmaW5kIiwib24iLCJhdHRyIiwiZ2VuZXJhdGVVSUQiLCJ0b1VwcGVyQ2FzZSIsInRyaWdnZXIiLCJ2YWwiLCJsZW5ndGgiLCJjaGFyc2V0IiwicGFzc3dvcmQiLCJpIiwicmFuZG9tSW5kZXgiLCJNYXRoIiwiZmxvb3IiLCJyYW5kb20iLCJjYkJlZm9yZVNlbmRGb3JtIiwic2V0dGluZ3MiLCJyZXN1bHQiLCJkYXRhIiwiZm9ybSIsImV4dGVuc2lvbnMiLCJwZWVycyIsImpvaW4iLCJjYkFmdGVyU2VuZEZvcm0iLCJGb3JtIiwidXJsIiwiZ2xvYmFsUm9vdFVybCIsImRvY3VtZW50IiwicmVhZHkiXSwibWFwcGluZ3MiOiI7O0FBQUE7QUFDQTtBQUNBO0FBQ0E7QUFDQTtBQUNBO0FBQ0E7QUFDQSxJQUFNQSxLQUFLLEdBQU8sd0JBQWxCO0FBQ0EsSUFBTUMsTUFBTSxHQUFNLDBCQUFsQjtBQUNBLElBQU1DLFNBQVMsR0FBRyxvQkFBbEI7QUFDQSxJQUFNQyxjQUFjLEdBQUcsc0JBQXZCO0FBRUE7O0FBQ0EsSUFBTUMsa0JBQWtCLEdBQUc7QUFDMUJDLEVBQUFBLFFBQVEsRUFBRUMsQ0FBQyxDQUFDLE1BQUlMLE1BQUwsQ0FEZTtBQUUxQk0sRUFBQUEsV0FBVyxFQUFFRCxDQUFDLENBQUMsTUFBSUwsTUFBSixHQUFXLGVBQVosQ0FGWTtBQUcxQk8sRUFBQUEsVUFBVSxFQUFFRixDQUFDLENBQUMsTUFBSUwsTUFBSixHQUFXLGVBQVosQ0FIYTtBQUkxQlEsRUFBQUEsaUJBQWlCLEVBQUVILENBQUMsQ0FBQyxNQUFJTCxNQUFKLEdBQVcsZUFBWixDQUpNOztBQU0xQjtBQUNEO0FBQ0E7QUFDQTtBQUNDUyxFQUFBQSxhQUFhLEVBQUU7QUFDZEMsSUFBQUEsb0JBQW9CLEVBQUU7QUFDckJDLE1BQUFBLFVBQVUsRUFBRSxzQkFEUztBQUVyQkMsTUFBQUEsS0FBSyxFQUFFLENBQ047QUFDQ0MsUUFBQUEsSUFBSSxFQUFFLG1CQURQO0FBRUNDLFFBQUFBLE1BQU0sRUFBRUMsZUFBZSxDQUFDQztBQUZ6QixPQURNO0FBRmMsS0FEUjtBQVVkQyxJQUFBQSxxQkFBcUIsRUFBRTtBQUN0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LCJzb3VyY2VzQ29udGVudCI6WyIvKlxuICogQ29weXJpZ2h0IChDKSBNSUtPIExMQyAtIEFsbCBSaWdodHMgUmVzZXJ2ZWRcbiAqIFVuYXV0aG9yaXplZCBjb3B5aW5nIG9mIHRoaXMgZmlsZSwgdmlhIGFueSBtZWRpdW0gaXMgc3RyaWN0bHkgcHJvaGliaXRlZFxuICogUHJvcHJpZXRhcnkgYW5kIGNvbmZpZGVudGlhbFxuICogV3JpdHRlbiBieSBOaWtvbGF5IEJla2V0b3YsIDExIDIwMThcbiAqXG4gKi9cbmNvbnN0IGlkVXJsICAgICA9ICdtb2R1bGUtY29ubmVjdG9yLWYtbS1jJztcbmNvbnN0IGlkRm9ybSAgICA9ICdtb2R1bGUtY29ubmVjdG9yZm1jLWZvcm0nO1xuY29uc3QgY2xhc3NOYW1lID0gJ01vZHVsZUNvbm5lY3RvckZNQyc7XG5jb25zdCBpbnB1dENsYXNzTmFtZSA9ICdtaWtvcGJ4LW1vZHVsZS1pbnB1dCc7XG5cbi8qIGdsb2JhbCBnbG9iYWxSb290VXJsLCBnbG9iYWxUcmFuc2xhdGUsIEZvcm0sIENvbmZpZywgJCAqL1xuY29uc3QgTW9kdWxlQ29ubmVjdG9yRk1DID0ge1xuXHQkZm9ybU9iajogJCgnIycraWRGb3JtKSxcblx0JGNoZWNrQm94ZXM6ICQoJyMnK2lkRm9ybSsnIC51aS5jaGVja2JveCcpLFxuXHQkZHJvcERvd25zOiAkKCcjJytpZEZvcm0rJyAudWkuZHJvcGRvd24nKSxcblx0JGRpc2FiaWxpdHlGaWVsZHM6ICQoJyMnK2lkRm9ybSsnICAuZGlzYWJpbGl0eScpLFxuXG5cdC8qKlxuXHQgKiBGaWVsZCB2YWxpZGF0aW9uIHJ1bGVzXG5cdCAqIGh0dHBzOi8vc2VtYW50aWMtdWkuY29tL2JlaGF2aW9ycy9mb3JtLmh0bWxcblx0ICovXG5cdHZhbGlkYXRlUnVsZXM6IHtcblx0XHRpbmNvbWluZ0VuZHBvaW50UG9ydDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRQb3J0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlOiAnaW50ZWdlclsxLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludFBvcnRJc0VtcHR5LFxuXHRcdFx0XHR9LFxuXHRcdFx0XSxcblx0XHR9LFxuXHRcdGluY29taW5nRW5kcG9pbnRMb2dpbjoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRMb2dpbicsXG5cdFx0XHRydWxlczogW1xuXHRcdFx0XHR7XG5cdFx0XHRcdFx0dHlwZSAgIDogJ2VtcHR5Jyxcblx0XHRcdFx0XHRwcm9tcHQ6IGdsb2JhbFRyYW5zbGF0ZS5tb2R1bGVfY29ubmVjdG9yZm1jX2luY29taW5nRW5kcG9pbnRMb2dpbklzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0aW5jb21pbmdFbmRwb2ludFNlY3JldDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRTZWNyZXQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdlbXB0eScsXG5cdFx0XHRcdFx0cHJvbXB0OiBnbG9iYWxUcmFuc2xhdGUubW9kdWxlX2Nvbm5lY3RvcmZtY19pbmNvbWluZ0VuZHBvaW50U2VjcmV0SXNFbXB0eSxcblx0XHRcdFx0fSxcblx0XHRcdF0sXG5cdFx0fSxcblx0XHRpbmNvbWluZ0VuZHBvaW50SG9zdDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2luY29taW5nRW5kcG9pbnRIb3N0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnZW1wdHknLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfaW5jb21pbmdFbmRwb2ludEhvc3RJc0VtcHR5LFxuXHRcdFx0XHR9LFxuXHRcdFx0XSxcblx0XHR9LFxuXHRcdHNpcFBvcnQ6IHtcblx0XHRcdGlkZW50aWZpZXI6ICdzaXBQb3J0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnaW50ZWdlcls1MTYwLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfc2lwUG9ydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0YW1pUG9ydDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ2FtaVBvcnQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdpbnRlZ2VyWzU1MDAwLi42MDAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfYW1pUG9ydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0cnRwUG9ydFN0YXJ0OiB7XG5cdFx0XHRpZGVudGlmaWVyOiAncnRwUG9ydFN0YXJ0Jyxcblx0XHRcdHJ1bGVzOiBbXG5cdFx0XHRcdHtcblx0XHRcdFx0XHR0eXBlICAgOiAnaW50ZWdlcls0MDAwMC4uNjUwMDBdJyxcblx0XHRcdFx0XHRwcm9tcHQ6IGdsb2JhbFRyYW5zbGF0ZS5tb2R1bGVfY29ubmVjdG9yZm1jX3J0cFBvcnRTdGFydElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdFx0cnRwUG9ydEVuZDoge1xuXHRcdFx0aWRlbnRpZmllcjogJ3J0cFBvcnRFbmQnLFxuXHRcdFx0cnVsZXM6IFtcblx0XHRcdFx0e1xuXHRcdFx0XHRcdHR5cGUgICA6ICdpbnRlZ2VyWzQwMDAwLi42NTAwMF0nLFxuXHRcdFx0XHRcdHByb21wdDogZ2xvYmFsVHJhbnNsYXRlLm1vZHVsZV9jb25uZWN0b3JmbWNfcnRwUG9ydEVuZElzRW1wdHksXG5cdFx0XHRcdH0sXG5cdFx0XHRdLFxuXHRcdH0sXG5cdH0sXG5cdC8qKlxuXHQgKiBPbiBwYWdlIGxvYWQgd2UgaW5pdCBzb21lIFNlbWFudGljIFVJIGxpYnJhcnlcblx0ICovXG5cdGluaXRpYWxpemUoKSB7XG5cdFx0Ly8g0LjQvdC40YbQuNCw0LvQuNC30LjRgNGD0LXQvCDRh9C10LrQsdC+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