Passed
Push — develop ( aa0db1...06f5e3 )
by Портнов
11:38
created

SIPConf::getDependenceModels()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
nc 1
nop 0
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2020 Alexey Portnov and Nikolay Beketov
5
 *
6
 * This program is free software: you can redistribute it and/or modify
7
 * it under the terms of the GNU General Public License as published by
8
 * the Free Software Foundation; either version 3 of the License, or
9
 * (at your option) any later version.
10
 *
11
 * This program is distributed in the hope that it will be useful,
12
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
 * GNU General Public License for more details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with this program.
17
 * If not, see <https://www.gnu.org/licenses/>.
18
 */
19
20
namespace MikoPBX\Core\Asterisk\Configs;
21
22
use MikoPBX\Common\Models\{Codecs,
23
    ExtensionForwardingRights,
24
    Extensions,
25
    NetworkFilters,
26
    OutgoingRoutingTable,
27
    PbxSettings,
28
    Sip,
29
    SipHosts,
30
    Users
31
};
32
use MikoPBX\Core\Asterisk\AstDB;
33
use MikoPBX\Core\Asterisk\Configs\Generators\Extensions\IncomingContexts;
34
use MikoPBX\Modules\Config\ConfigClass;
35
use MikoPBX\Core\System\{MikoPBXConfig, Network, Processes, Util};
36
use MikoPBX\Core\Utilities\SubnetCalculator;
37
use Phalcon\Di;
38
use Throwable;
39
40
class SIPConf extends CoreConfigClass
41
{
42
    public const TYPE_PJSIP = 'PJSIP';
43
    private const TOPOLOGY_HASH_FILE = '/topology_hash';
44
45
    protected $data_peers;
46
    protected $data_providers;
47
    protected $data_rout;
48
    protected array $dataSipHosts;
49
50
    protected string $technology;
51
    protected array $contexts_data;
52
53
    protected string $description = 'pjsip.conf';
54
55
    /**
56
     *
57
     * @return array
58
     */
59
    public function getDependenceModels(): array
60
    {
61
        return [Sip::class, Users::class, SipHosts::class];
62
    }
63
64
    /**
65
     * Проверка ключевых параметров.
66
     * Если параметры изменены, то необходим рестарт Asterisk процесса.
67
     *
68
     * @return bool
69
     */
70
    public function needAsteriskRestart(): bool
71
    {
72
        $di = Di::getDefault();
73
        if ($di === null) {
74
            return false;
75
        }
76
        $mikoPBXConfig = new MikoPBXConfig();
77
        [$topology, $extIpAddress, $externalHostName, $subnets] = $this->getTopologyData();
78
79
        $generalSettings = $mikoPBXConfig->getGeneralSettings();
80
        $now_hash        = md5($topology . $externalHostName . $extIpAddress . $generalSettings['SIPPort']. $generalSettings['TLS_PORT'] . implode('',$subnets));
81
        $old_hash        = '';
82
        $varEtcDir       = $di->getShared('config')->path('core.varEtcDir');
83
        if (file_exists($varEtcDir . self::TOPOLOGY_HASH_FILE)) {
84
            $old_hash = file_get_contents($varEtcDir . self::TOPOLOGY_HASH_FILE);
85
        }
86
87
        return $old_hash !== $now_hash;
88
    }
89
90
    /**
91
     * @return array
92
     */
93
    private function getTopologyData(): array
94
    {
95
        $network = new Network();
96
97
        $topology    = 'public';
98
        $extipaddr   = '';
99
        $exthostname = '';
100
        $networks    = $network->getEnabledLanInterfaces();
101
        $subnets     = ['127.0.0.1/32'];
102
        foreach ($networks as $if_data) {
103
            $lan_config = $network->getInterface($if_data['interface']);
104
            if (empty($lan_config['ipaddr']) || empty($lan_config['subnet'])) {
105
                continue;
106
            }
107
            try {
108
                $sub = new SubnetCalculator($lan_config['ipaddr'], $lan_config['subnet']);
109
            } catch (Throwable $e) {
110
                Util::sysLogMsg(self::class, $e->getMessage(), LOG_ERR);
111
                continue;
112
            }
113
            $net = $sub->getNetworkPortion() . '/' . $lan_config['subnet'];
114
            if ($if_data['topology'] === 'private' && in_array($net, $subnets, true) === false) {
115
                $subnets[] = $net;
116
            }
117
            if (trim($if_data['internet']) === '1') {
118
                $topology    = trim($if_data['topology']);
119
                $extipaddr   = trim($if_data['extipaddr']);
120
                $exthostname = trim($if_data['exthostname']);
121
            }
122
        }
123
124
        $networks = NetworkFilters::find('local_network=1');
125
        foreach ($networks as $net) {
126
            if (in_array($net->permit, $subnets, true) === false) {
127
                $subnets[] = $net->permit;
128
            }
129
        }
130
131
        return [
132
            $topology,
133
            $extipaddr,
134
            $exthostname,
135
            $subnets,
136
        ];
137
    }
138
139
    /**
140
     * Генератор extension для контекста peers.
141
     *
142
     * @return string
143
     */
144
    public function extensionGenContexts(): string
145
    {
146
        if ($this->data_peers === null) {
147
            $this->getSettings();
148
        }
149
        // Генерация внутреннего номерного плана.
150
        $conf = '';
151
152
        $contexts = [];
153
        // Входящие контексты.
154
        foreach ($this->data_providers as $provider) {
155
            $contextsData = $this->contexts_data[$provider['context_id']];
156
            if (count($contextsData) === 1) {
157
                $conf .= IncomingContexts::generate($provider['uniqid'], $provider['username']);
158
            } elseif ( ! in_array($provider['context_id'], $contexts, true)) {
159
                $conf       .= IncomingContexts::generate(
160
                    $contextsData,
161
                    null,
162
                    $provider['context_id']
163
                );
164
                $contexts[] = $provider['context_id'];
165
            }
166
        }
167
168
        return $conf;
169
    }
170
171
    /**
172
     * Получение настроек.
173
     */
174
    public function getSettings(): void
175
    {
176
        $this->contexts_data = [];
177
        // Настройки для текущего класса.
178
        $this->data_peers        = $this->getPeers();
179
        $this->data_providers    = $this->getProviders();
180
        $this->data_rout         = $this->getOutRoutes();
181
        $this->technology        = self::getTechnology();
182
        $this->dataSipHosts      = self::getSipHosts();
183
    }
184
185
    /**
186
     * Получение данных по SIP пирам.
187
     *
188
     * @return array
189
     */
190
    private function getPeers(): array
191
    {
192
        /** @var NetworkFilters $network_filter */
193
        /** @var Sip $sip_peer */
194
        /** @var Extensions $extension */
195
        /** @var Users $user */
196
        /** @var ExtensionForwardingRights $extensionForwarding */
197
198
        $data    = [];
199
        $db_data = Sip::find("type = 'peer' AND ( disabled <> '1')");
200
        foreach ($db_data as $sip_peer) {
201
            $arr_data       = $sip_peer->toArray();
202
            $network_filter = null;
203
            if ( ! empty($sip_peer->networkfilterid)) {
204
                $network_filter = NetworkFilters::findFirst($sip_peer->networkfilterid);
205
            }
206
            $arr_data['permit'] = ($network_filter === null) ? '' : $network_filter->permit;
207
            $arr_data['deny']   = ($network_filter === null) ? '' : $network_filter->deny;
208
209
            // Получим используемые кодеки.
210
            $arr_data['codecs'] = $this->getCodecs();
211
212
            // Имя сотрудника.
213
            $extension = Extensions::findFirst("number = '{$sip_peer->extension}'");
214
            if (null === $extension) {
215
                $arr_data['publicaccess'] = false;
216
                $arr_data['language']     = '';
217
                $arr_data['calleridname'] = $sip_peer->extension;
218
            } else {
219
                $arr_data['publicaccess'] = $extension->public_access;
220
                $arr_data['calleridname'] = $extension->callerid;
221
                $user                     = Users::findFirst($extension->userid);
222
                if (null !== $user) {
223
                    $arr_data['language'] = $user->language;
224
                    $arr_data['user_id']  = $user->id;
225
                }
226
            }
227
            $extensionForwarding = ExtensionForwardingRights::findFirst("extension = '{$sip_peer->extension}'");
228
            if (null === $extensionForwarding) {
229
                $arr_data['ringlength']              = '';
230
                $arr_data['forwarding']              = '';
231
                $arr_data['forwardingonbusy']        = '';
232
                $arr_data['forwardingonunavailable'] = '';
233
            } else {
234
                $arr_data['ringlength']              = $extensionForwarding->ringlength;
235
                $arr_data['forwarding']              = $extensionForwarding->forwarding;
236
                $arr_data['forwardingonbusy']        = $extensionForwarding->forwardingonbusy;
237
                $arr_data['forwardingonunavailable'] = $extensionForwarding->forwardingonunavailable;
238
            }
239
            $data[] = $arr_data;
240
        }
241
242
        return $data;
243
    }
244
245
    /**
246
     * Возвращает доступные пиру кодеки.
247
     *
248
     * @return array
249
     */
250
    private function getCodecs(): array
251
    {
252
        $arr_codecs = [];
253
        $filter     = [
254
            'conditions' => 'disabled="0"',
255
            'order'      => 'type, priority',
256
        ];
257
        $codecs     = Codecs::find($filter);
258
        foreach ($codecs as $codec_data) {
259
            $arr_codecs[] = $codec_data->name;
260
        }
261
262
        return $arr_codecs;
263
    }
264
265
    /**
266
     * Получение данных по SIP провайдерам.
267
     *
268
     * @return array
269
     */
270
    private function getProviders(): array
271
    {
272
        /** @var Sip $sip_peer */
273
        /** @var NetworkFilters $network_filter */
274
        // Получим настройки всех аккаунтов.
275
        $data    = [];
276
        $db_data = Sip::find("type = 'friend' AND ( disabled <> '1')");
277
        foreach ($db_data as $sip_peer) {
278
            $arr_data                               = $sip_peer->toArray();
279
            $network_filter                         = NetworkFilters::findFirst($sip_peer->networkfilterid);
280
            $arr_data['permit']                     = ($network_filter === null) ? '' : $network_filter->permit;
281
            $arr_data['deny']                       = ($network_filter === null) ? '' : $network_filter->deny;
282
            // Получим используемые кодеки.
283
            $arr_data['codecs'] = $this->getCodecs();
284
            $context_id = self::getContextId($sip_peer->host.$sip_peer->port);
285
            if ( ! isset($this->contexts_data[$context_id])) {
286
                $this->contexts_data[$context_id] = [];
287
            }
288
            $this->contexts_data[$context_id][$sip_peer->uniqid] = $sip_peer->username;
289
            $arr_data['context_id'] = $context_id;
290
            if(empty($arr_data['registration_type'])){
291
                if($sip_peer->noregister === '0'){
292
                    $arr_data['registration_type'] = Sip::REG_TYPE_OUTBOUND;
293
                }else{
294
                    $arr_data['registration_type'] = Sip::REG_TYPE_NONE;
295
                }
296
            }
297
            $arr_data['port']  = (trim($arr_data['port']) === '') ? '5060' : $arr_data['port'];
298
            $data[]                 = $arr_data;
299
        }
300
301
        return $data;
302
    }
303
304
    /**
305
     * Генератор исходящих контекстов для пиров.
306
     *
307
     * @return array
308
     */
309
    private function getOutRoutes(): array
310
    {
311
        if ($this->data_peers === null) {
312
            $this->getSettings();
313
        }
314
        /** @var OutgoingRoutingTable $rout */
315
        /** @var OutgoingRoutingTable $routs */
316
        /** @var Sip $db_data */
317
        /** @var Sip $sip_peer */
318
319
        $data    = [];
320
        $routs   = OutgoingRoutingTable::find(['order' => 'priority']);
321
        $db_data = Sip::find("type = 'friend' AND ( disabled <> '1')");
322
        foreach ($routs as $rout) {
323
            foreach ($db_data as $sip_peer) {
324
                if ($sip_peer->uniqid !== $rout->providerid) {
325
                    continue;
326
                }
327
                $arr_data                = $rout->toArray();
328
                $arr_data['description'] = $sip_peer->description;
329
                $arr_data['uniqid']      = $sip_peer->uniqid;
330
                $data[]                  = $arr_data;
331
            }
332
        }
333
334
        return $data;
335
    }
336
337
    /**
338
     * Returns PJSIP ot SIP uses at PBX
339
     *
340
     * @return string
341
     */
342
    public static function getTechnology(): string
343
    {
344
        return self::TYPE_PJSIP;
345
    }
346
347
    /**
348
     * Возвращает массив хостов.
349
     *
350
     * @return array
351
     */
352
    public static function getSipHosts(): array
353
    {
354
        $dataSipHosts = [];
355
        /** @var SipHosts $sipHosts */
356
        /** @var SipHosts $hostData */
357
        $sipHosts = SipHosts::find();
358
        foreach ($sipHosts as $hostData) {
359
            if ( ! isset($dataSipHosts[$hostData->provider_id])) {
360
                $dataSipHosts[$hostData->provider_id] = [];
361
            }
362
            $dataSipHosts[$hostData->provider_id][] = str_replace(PHP_EOL, '', $hostData->address);
363
        }
364
365
        return $dataSipHosts;
366
    }
367
368
    /**
369
     * Генерация хинтов.
370
     *
371
     * @return string
372
     */
373
    public function extensionGenHints(): string
374
    {
375
        if ($this->data_peers === null) {
376
            $this->getSettings();
377
        }
378
        $conf = '';
379
        foreach ($this->data_peers as $peer) {
380
            $hint = "{$this->technology}/{$peer['extension']}";
381
            if($this->generalSettings['UseWebRTC'] === '1') {
382
                $hint.="&{$this->technology}/{$peer['extension']}-WS";
383
            }
384
            $conf .= "exten => {$peer['extension']},hint,$hint&Custom:{$peer['extension']} \n";
385
        }
386
        return $conf;
387
    }
388
389
    public function extensionGenInternal(): string
390
    {
391
        if ($this->data_peers === null) {
392
            $this->getSettings();
393
        }
394
        // Генерация внутреннего номерного плана.
395
        $conf = '';
396
        foreach ($this->data_peers as $peer) {
397
            $conf .= "exten => {$peer['extension']},1,Goto(internal-users,{$peer['extension']},1) \n";
398
        }
399
        $conf .= "\n";
400
401
        return $conf;
402
    }
403
404
    public function extensionGenInternalTransfer(): string
405
    {
406
        if ($this->data_peers === null) {
407
            $this->getSettings();
408
        }
409
        // Генерация внутреннего номерного плана.
410
        $conf = '';
411
        foreach ($this->data_peers as $peer) {
412
            $conf .= "exten => {$peer['extension']},1,Set(__ISTRANSFER=transfer_) \n";
413
            $conf .= "	same => n,Goto(internal-users,{$peer['extension']},1) \n";
414
        }
415
        $conf .= "\n";
416
417
        return $conf;
418
    }
419
420
    /**
421
     * Генератор sip.conf
422
     *
423
     * @return void
424
     */
425
    protected function generateConfigProtected(): void
426
    {
427
        $conf  = $this->generateGeneralPj();
428
        $conf .= $this->generateProvidersPj();
429
        $conf .= $this->generatePeersPj();
430
431
        $astEtcDir = $this->config->path('asterisk.astetcdir');
432
433
        Util::fileWriteContent($astEtcDir . '/pjsip.conf', $conf);
434
        $pjConf = '[log_mappings]' . "\n" .
435
            'type=log_mappings' . "\n" .
436
            'asterisk_error = 0' . "\n" .
437
            'asterisk_warning = 2' . "\n" .
438
            'asterisk_debug = 1,3,4,5,6' . "\n\n";
439
        file_put_contents($astEtcDir.'/pjproject.conf', $pjConf);
440
        file_put_contents($astEtcDir.'/sorcery.conf', '');
441
        $this->updateAsteriskDatabase();
442
    }
443
444
    /**
445
     * Обновление маршрутизации в AstDB
446
     * @return bool
447
     */
448
    public function updateAsteriskDatabase():bool
449
    {
450
        if ($this->data_peers === null) {
451
            $this->getSettings();
452
        }
453
        $warError = false;
454
        $db = new AstDB();
455
        foreach ($this->data_peers as $peer) {
456
            // Помещаем в AstDB сведения по маршуртизации.
457
            $ringLength = ($peer['ringlength'] === '0') ? '' : trim($peer['ringlength']);
458
            $warError |= !$db->databasePut('FW_TIME', $peer['extension'], $ringLength);
459
            $warError |= !$db->databasePut('FW', $peer['extension'], trim($peer['forwarding']));
460
            $warError |= !$db->databasePut('FW_BUSY', $peer['extension'], trim($peer['forwardingonbusy']));
461
            $warError |= !$db->databasePut('FW_UNAV', $peer['extension'], trim($peer['forwardingonunavailable']));
462
        }
463
464
        return !$warError;
465
    }
466
467
    /**
468
     * Генератора секции general pjsip.conf
469
     *
470
     *
471
     * @return string
472
     */
473
    private function generateGeneralPj(): string
474
    {
475
        $lang = $this->generalSettings['PBXLanguage'];
476
        [$topology, $extIpAddress, $externalHostName, $subnets] = $this->getTopologyData();
477
478
        $codecs    = $this->getCodecs();
479
        $codecConf = '';
480
        foreach ($codecs as $codec) {
481
            $codecConf .= "allow = $codec\n";
482
        }
483
        $pbxVersion = $this->generalSettings['PBXVersion'];
484
        $sipPort    = $this->generalSettings['SIPPort'];
485
        $tlsPort    = $this->generalSettings['TLS_PORT'];
486
        $natConf    = '';
487
        $tlsNatConf = '';
488
489
        $resolveOk = Processes::mwExec("timeout 1 getent hosts '$externalHostName'") === 0;
490
        if(!empty($externalHostName) && !$resolveOk){
491
            Util::sysLogMsg('DNS', "ERROR: DNS $externalHostName not resolved, It will not be used in SIP signaling.");
492
        }
493
        if ($topology === 'private') {
494
            foreach ($subnets as $net) {
495
                $natConf .= "local_net=$net\n";
496
            }
497
            if ( !empty($externalHostName) && $resolveOk ) {
498
                $parts   = explode(':', $externalHostName);
499
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
500
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
501
                $tlsNatConf = "{$natConf}external_signaling_port=$tlsPort";
502
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? $sipPort);
503
            } elseif ( ! empty($extIpAddress)) {
504
                $parts   = explode(':', $extIpAddress);
505
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
506
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
507
                $tlsNatConf = "{$natConf}external_signaling_port=$tlsPort";
508
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? $sipPort);
509
            }
510
        }
511
512
        $typeTransport = 'type = transport';
513
        $conf = "[global] \n" .
514
            "type = global\n" .
515
            "disable_multi_domain=yes\n" .
516
            "endpoint_identifier_order=username,ip,anonymous\n" .
517
            "user_agent = mikopbx-$pbxVersion\n\n" .
518
519
            "[transport-udp]\n" .
520
            "$typeTransport\n" .
521
            "protocol = udp\n" .
522
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
523
            "$natConf\n\n" .
524
525
            "[transport-tcp]\n" .
526
            "$typeTransport\n" .
527
            "protocol = tcp\n" .
528
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
529
            "$natConf\n\n" .
530
531
            "[transport-tls]\n" .
532
            "$typeTransport\n" .
533
            "protocol = tls\n" .
534
            "bind=0.0.0.0:{$tlsPort}\n" .
535
            "cert_file=/etc/asterisk/keys/ajam.pem\n" .
536
            "priv_key_file=/etc/asterisk/keys/ajam.pem\n" .
537
            // "ca_list_file=/etc/ssl/certs/ca-certificates.crt\n".
538
            // "verify_server=yes\n".
539
            "method=sslv23\n" .
540
            "$tlsNatConf\n\n" .
541
542
            "[transport-wss]\n" .
543
            "$typeTransport\n" .
544
            "protocol = wss\n" .
545
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
546
            "$natConf\n\n";
547
548
        $allowGuestCalls = PbxSettings::getValueByKey('PBXAllowGuestCalls');
549
        if ($allowGuestCalls === '1') {
550
            $conf .= "[anonymous]\n" .
551
                "type = endpoint\n" .
552
                $codecConf .
553
                "language=$lang\n" .
554
                "timers = no\n" .
555
                "context = public-direct-dial\n\n";
556
        }
557
558
        $varEtcDir = $this->config->path('core.varEtcDir');
559
        $hash = md5($topology . $externalHostName . $extIpAddress . $this->generalSettings['SIPPort']. $this->generalSettings['TLS_PORT'] . implode('',$subnets));
560
        file_put_contents($varEtcDir.self::TOPOLOGY_HASH_FILE, $hash);
561
        $conf .= "\n";
562
        return $conf;
563
    }
564
565
    /**
566
     * Генератор секции провайдеров в sip.conf
567
     *
568
     *
569
     * @return string
570
     */
571
    private function generateProvidersPj(): string
572
    {
573
        $conf        = '';
574
        $reg_strings = '';
575
        $prov_config = '';
576
        if ($this->data_providers === null) {
577
            $this->getSettings();
578
        }
579
        foreach ($this->data_providers as $provider) {
580
            $manual_attributes = Util::parseIniSettings(base64_decode($provider['manualattributes'] ?? ''));
581
582
            if($provider['registration_type'] === Sip::REG_TYPE_OUTBOUND){
583
                $reg_strings .= $this->generateProviderRegistrationAuth($provider, $manual_attributes);
584
                $reg_strings .= $this->generateProviderRegistration($provider, $manual_attributes);
585
            }
586
            if($provider['registration_type'] !== Sip::REG_TYPE_NONE){
587
                $prov_config .= $this->generateProviderAuth($provider, $manual_attributes);
588
            }
589
            if($provider['registration_type'] !== Sip::REG_TYPE_INBOUND) {
590
                $prov_config .= $this->generateProviderIdentify($provider, $manual_attributes);
591
            }
592
            $prov_config .= $this->generateProviderAor($provider, $manual_attributes);
593
            $prov_config .= $this->generateProviderEndpoint($provider, $manual_attributes);
594
        }
595
596
        $conf .= $reg_strings;
597
        $conf .= $prov_config;
598
599
        return $conf;
600
    }
601
602
    /**
603
     * Генерация Auth для секции Registration провайдера.
604
     *
605
     * @param array $provider
606
     * @param array $manual_attributes
607
     *
608
     * @return string
609
     */
610
    private function generateProviderRegistrationAuth(array $provider, array $manual_attributes): string {
611
        $conf = '';
612
        $options         = [
613
            'type'     => 'registration-auth',
614
            'username' => $provider['username'],
615
            'password' => $provider['secret'],
616
        ];
617
        $options         = $this->overridePJSIPOptionsFromModules(
618
            $provider['uniqid'],
619
            $options,
620
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
621
        );
622
        $options['type'] = 'auth';
623
        $conf            .= "[REG-AUTH-{$provider['uniqid']}]\n";
624
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration-auth');
625
        return $conf;
626
    }
627
628
    /**
629
     * Calls an overridePJSIPOptions function from additional modules
630
     *
631
     * @param $extensionOrId
632
     * @param $options
633
     * @param $method
634
     *
635
     * @return array
636
     */
637
    private function overridePJSIPOptionsFromModules($extensionOrId, $options, $method): array
638
    {
639
        $configClassObj = new ConfigClass();
640
        $modulesOverridingArrays = $configClassObj->hookModulesMethodWithArrayResult($method, [$extensionOrId, $options]);
641
        foreach ($modulesOverridingArrays as $newOptionsSet) {
642
            // How to make some order of overrides?
643
            $options = $newOptionsSet;
644
        }
645
        return $options;
646
    }
647
648
    /**
649
     * Генерация Registration провайдера.
650
     *
651
     * @param array $provider
652
     * @param array $manual_attributes
653
     *
654
     * @return string
655
     */
656
    private function generateProviderRegistration(array $provider, array $manual_attributes): string {
657
        $conf = '';
658
        $options = [
659
            'type'                     => 'registration',
660
            'outbound_auth'            => "REG-AUTH-{$provider['uniqid']}",
661
            'contact_user'             => $provider['username'],
662
            'retry_interval'           => '30',
663
            'max_retries'              => '100',
664
            'forbidden_retry_interval' => '300',
665
            'fatal_retry_interval'     => '300',
666
            'expiration'               => $this->generalSettings['SIPDefaultExpiry'],
667
            'server_uri'               => "sip:{$provider['host']}:{$provider['port']}",
668
            'client_uri'               => "sip:{$provider['username']}@{$provider['host']}:{$provider['port']}",
669
        ];
670
671
        if(!empty($provider['transport'])){
672
            $options['transport'] = "transport-{$provider['transport']}";
673
        }
674
        if(!empty($provider['outbound_proxy'])){
675
            $options['outbound_proxy'] = "sip:{$provider['outbound_proxy']}\;lr";
676
        }
677
        $options = $this->overridePJSIPOptionsFromModules(
678
            $provider['uniqid'],
679
            $options,
680
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
681
        );
682
        $conf    .= "[REG-{$provider['uniqid']}] \n";
683
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration');
684
685
        return $conf;
686
    }
687
688
    /**
689
     * Генерация Auth провайдера.
690
     *
691
     * @param array $provider
692
     * @param array $manual_attributes
693
     *
694
     * @return string
695
     */
696
    private function generateProviderAuth(array $provider, array $manual_attributes): string {
697
        $conf = '';
698
        $options         = [
699
            'type'     => 'endpoint-auth',
700
            'username' => $provider['username'],
701
            'password' => $provider['secret'],
702
        ];
703
        $options         = $this->overridePJSIPOptionsFromModules(
704
            $provider['uniqid'],
705
            $options,
706
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
707
        );
708
        $options['type'] = 'auth';
709
        $conf            .= "[{$provider['uniqid']}-AUTH]\n";
710
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint-auth');
711
712
        return $conf;
713
    }
714
715
    /**
716
     * Генерация AOR для Endpoint.
717
     *
718
     * @param array $provider
719
     * @param array $manual_attributes
720
     *
721
     * @return string
722
     */
723
    private function generateProviderAor(array $provider, array $manual_attributes): string
724
    {
725
        $conf        = '';
726
        $contact     = '';
727
        if($provider['registration_type'] === Sip::REG_TYPE_OUTBOUND){
728
            $contact = "sip:{$provider['username']}@{$provider['host']}:{$provider['port']}";
729
        }elseif($provider['registration_type'] === Sip::REG_TYPE_NONE) {
730
            $contact = "sip:{$provider['host']}:{$provider['port']}";
731
        }
732
        $options = [
733
            'type'               => 'aor',
734
            'max_contacts'       => '1',
735
            'maximum_expiration' => $this->generalSettings['SIPMaxExpiry'],
736
            'minimum_expiration' => $this->generalSettings['SIPMinExpiry'],
737
            'default_expiration' => $this->generalSettings['SIPDefaultExpiry'],
738
        ];
739
        if(!empty($contact)){
740
            $options['contact'] = $contact;
741
        }
742
743
        if ($provider['qualify'] === '1') {
744
            $options['qualify_frequency'] = $provider['qualifyfreq'];
745
            $options['qualify_timeout']   = '3.0';
746
        }
747
        if(!empty($provider['outbound_proxy'])){
748
            $options['outbound_proxy'] = "sip:{$provider['outbound_proxy']}\;lr";
749
        }
750
        $options = $this->overridePJSIPOptionsFromModules(
751
            $provider['uniqid'],
752
            $options,
753
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
754
        );
755
        $conf    .= "[{$provider['uniqid']}]\n";
756
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
757
758
        return $conf;
759
    }
760
761
    /**
762
     * Генерация AOR для Endpoint.
763
     *
764
     * @param array $provider
765
     * @param array $manual_attributes
766
     *
767
     * @return string
768
     */
769
    private function generateProviderIdentify(array $provider, array $manual_attributes): string {
770
        $conf          = '';
771
        $providerHosts = $this->dataSipHosts[$provider['uniqid']] ?? [];
772
        if ( ! in_array($provider['host'], $providerHosts, true)) {
773
            $providerHosts[] = $provider['host'];
774
        }
775
        if(!empty($provider['outbound_proxy'])){
776
            $providerHosts[] = explode(':', $provider['outbound_proxy'])[0];
777
        }
778
        $options = [
779
            'type'     => 'identify',
780
            'endpoint' => $provider['uniqid'],
781
            'match'    => implode(',', array_unique($providerHosts)),
782
        ];
783
784
        $options = $this->overridePJSIPOptionsFromModules(
785
            $provider['uniqid'],
786
            $options,
787
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
788
        );
789
        $conf    .= "[{$provider['uniqid']}]\n";
790
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'identify');
791
        return $conf;
792
    }
793
794
    /**
795
     * Генерация Endpoint провайдера.
796
     *
797
     * @param array $provider
798
     * @param array $manual_attributes
799
     *
800
     * @return string
801
     */
802
    private function generateProviderEndpoint(array $provider, array $manual_attributes): string {
803
        $conf       = '';
804
        $fromdomain = (trim($provider['fromdomain']) === '') ? $provider['host'] : $provider['fromdomain'];
805
        $from       = (trim($provider['fromuser']) === '') ? "{$provider['username']}; username" : "{$provider['fromuser']}; fromuser";
806
807
        if($provider['disablefromuser'] === '1'){
808
            $from_user   = null;
809
            $contactUser = trim($provider['username']??'');
810
        }else{
811
            $from_user   = $from;
812
            $contactUser = $from;
813
        }
814
815
        $language   = $this->generalSettings['PBXLanguage'];
816
817
        if (count($this->contexts_data[$provider['context_id']]) === 1) {
818
            $context_id = $provider['uniqid'];
819
        } else {
820
            $context_id = $provider['context_id'];
821
        }
822
        $dtmfmode = ($provider['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $provider['dtmfmode'];
823
        $options  = [
824
            'type'            => 'endpoint',
825
            '100rel'          => "no",
826
            'context'         => "{$context_id}-incoming",
827
            'dtmf_mode'       => $dtmfmode,
828
            'disallow'        => 'all',
829
            'allow'           => $provider['codecs'],
830
            'rtp_symmetric'   => 'yes',
831
            'force_rport'     => 'yes',
832
            'rewrite_contact' => 'yes',
833
            'ice_support'     => 'no',
834
            'direct_media'    => 'no',
835
            'from_user'       => $from_user,
836
            'from_domain'     => $fromdomain,
837
            'contact_user'    => $contactUser,
838
            'sdp_session'     => 'mikopbx',
839
            'language'        => $language,
840
            'aors'            => $provider['uniqid'],
841
            'timers'          => ' no',
842
        ];
843
844
        if(!empty($provider['transport'])){
845
            $options['transport'] = "transport-{$provider['transport']}";
846
            if($provider['transport'] === Sip::TRANSPORT_TLS){
847
                $options['media_encryption'] = 'sdes';
848
            }
849
        }
850
        if(!empty($provider['outbound_proxy'])){
851
            $options['outbound_proxy'] = "sip:{$provider['outbound_proxy']}\;lr";
852
        }
853
        if ($provider['registration_type'] === Sip::REG_TYPE_OUTBOUND) {
854
            $options['outbound_auth'] = "{$provider['uniqid']}-AUTH";
855
        }elseif ($provider['registration_type'] === Sip::REG_TYPE_INBOUND){
856
            $options['auth'] = "{$provider['uniqid']}-AUTH";
857
        }
858
        self::getToneZone($options, $language);
859
        $options = $this->overridePJSIPOptionsFromModules(
860
            $provider['uniqid'],
861
            $options,
862
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
863
        );
864
        $conf    .= "[{$provider['uniqid']}]".PHP_EOL;
865
        $conf    .= 'set_var=contextID='.$provider['context_id'].PHP_EOL;
866
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
867
868
        return $conf;
869
    }
870
871
    /**
872
     * Возвращает имя контекста без спецсимволовю
873
     * @param $name
874
     * @return string
875
     */
876
    public static function getContextId(string $name = ''):string
877
    {
878
        return preg_replace("/[^a-z\d]/iu", '', $name).'-incoming';
879
    }
880
881
    /**
882
     * @param array  $options
883
     * @param string $lang
884
     */
885
    public static function getToneZone(array &$options, string $lang): void
886
    {
887
        $settings = [
888
            'ru-ru' => 'ru',
889
            'en-gb' => 'uk',
890
            'de-de' => 'de',
891
            'da-dk' => 'dk',
892
            'es-es' => 'es',
893
            'gr-gr' => 'gr',
894
            'fr-ca' => 'fr',
895
            'it-it' => 'it',
896
            'ja-jp' => 'jp',
897
            'nl-nl' => 'nl',
898
            'pl-pl' => 'pl',
899
            'pt-br' => 'pt',
900
        ];
901
        $toneZone = $settings[$lang] ?? '';
902
        if ( ! empty($toneZone)) {
903
            $options['inband_progress'] = 'yes';
904
            $options['tone_zone']       = $toneZone;
905
        }
906
    }
907
908
    /**
909
     * Генератор сеции пиров для sip.conf
910
     *
911
     *
912
     * @return string
913
     */
914
    public function generatePeersPj(): string
915
    {
916
        if ($this->data_peers === null) {
917
            $this->getSettings();
918
        }
919
        $lang = $this->generalSettings['PBXLanguage'];
920
        $conf = '';
921
922
        foreach ($this->data_peers as $peer) {
923
            $manual_attributes = Util::parseIniSettings($peer['manualattributes'] ?? '');
924
            $conf              .= $this->generatePeerAuth($peer, $manual_attributes);
925
            $conf              .= $this->generatePeerAor($peer, $manual_attributes);
926
            $conf              .= $this->generatePeerEndpoint($lang, $peer, $manual_attributes);
927
        }
928
929
        $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEERS_PJ);
930
931
        return $conf;
932
    }
933
934
    /**
935
     * Генерация AOR для Endpoint.
936
     *
937
     * @param array $peer
938
     * @param array $manual_attributes
939
     *
940
     * @return string
941
     */
942
    private function generatePeerAuth(array $peer, array $manual_attributes): string
943
    {
944
        $conf    = '';
945
        $options = [
946
            'type'     => 'auth',
947
            'username' => $peer['extension'],
948
            'password' => $peer['secret'],
949
        ];
950
        $options = $this->overridePJSIPOptionsFromModules(
951
            $peer['extension'],
952
            $options,
953
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
954
        );
955
        $conf    .= "[{$peer['extension']}] \n";
956
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'auth');
957
958
        return $conf;
959
    }
960
961
    /**
962
     * Генерация AOR для Endpoint.
963
     *
964
     * @param array $peer
965
     * @param array $manual_attributes
966
     *
967
     * @return string
968
     */
969
    private function generatePeerAor(array $peer, array $manual_attributes): string
970
    {
971
        $conf    = '';
972
        $options = [
973
            'type'              => 'aor',
974
            'qualify_frequency' => '60',
975
            'qualify_timeout'   => '5',
976
            'max_contacts'      => '5',
977
        ];
978
        $options = $this->overridePJSIPOptionsFromModules(
979
            $peer['extension'],
980
            $options,
981
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
982
        );
983
        $conf    .= "[{$peer['extension']}]\n";
984
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
985
986
        if($this->generalSettings['UseWebRTC'] === '1'){
987
            $conf    .= "[{$peer['extension']}-WS]\n";
988
            $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
989
        }
990
991
        return $conf;
992
    }
993
994
    /**
995
     * Генерация endpoint.
996
     *
997
     * @param        $lang
998
     * @param array  $peer
999
     * @param array  $manual_attributes
1000
     *
1001
     * @return string
1002
     */
1003
    private function generatePeerEndpoint(
1004
        $lang,
1005
        array $peer,
1006
        array $manual_attributes
1007
    ): string {
1008
        $conf     = '';
1009
        $language = str_replace('_', '-', strtolower($lang));
1010
        $language = (trim($language) === '') ? 'en-en' : $language;
1011
1012
        $calleridname = (trim($peer['calleridname']) === '') ? $peer['extension'] : $peer['calleridname'];
1013
        if(mb_strlen($calleridname) !== strlen($calleridname)){
1014
            // Ограничим длину calleridname. Это Unicode символы. Ограничиваем длину.
1015
            $calleridname = mb_substr($calleridname,0, 40);
1016
        }
1017
        $busylevel    = (trim($peer['busylevel']) === '') ? '1' : '' . $peer['busylevel'];
1018
1019
        $dtmfmode = ($peer['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $peer['dtmfmode'];
1020
        $options  = [
1021
            'type'                 => 'endpoint',
1022
            'context'              => 'all_peers',
1023
            'dtmf_mode'            => $dtmfmode,
1024
            'disallow'             => 'all',
1025
            'allow'                => $peer['codecs'],
1026
            'rtp_symmetric'        => 'yes',
1027
            'force_rport'          => 'yes',
1028
            'rewrite_contact'      => 'yes',
1029
            'ice_support'          => 'no',
1030
            'direct_media'         => 'no',
1031
            'callerid'             => "{$calleridname} <{$peer['extension']}>",
1032
            'send_pai'             => 'yes',
1033
            'call_group'           => '1',
1034
            'pickup_group'         => '1',
1035
            'sdp_session'          => 'mikopbx',
1036
            'language'             => $language,
1037
            'device_state_busy_at' => $busylevel,
1038
            'aors'                 => $peer['extension'],
1039
            'auth'                 => $peer['extension'],
1040
            'outbound_auth'        => $peer['extension'],
1041
            'acl'                  => "acl_{$peer['extension']}",
1042
            'timers'               => 'no',
1043
            'message_context'      => 'messages',
1044
        ];
1045
1046
        if(!empty($peer['transport'])){
1047
            $options['transport'] = "transport-{$peer['transport']}";
1048
            if($peer['transport'] === Sip::TRANSPORT_TLS){
1049
                $options['media_encryption'] = 'sdes';
1050
            }
1051
        }
1052
1053
        self::getToneZone($options, $language);
1054
        $options = $this->overridePJSIPOptionsFromModules(
1055
            $peer['extension'],
1056
            $options,
1057
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
1058
        );
1059
        $conf    .= "[{$peer['extension']}] \n";
1060
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
1061
        $conf    .= $this->hookModulesMethod(self::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
1062
1063
        if($this->generalSettings['UseWebRTC'] === '1') {
1064
            unset($options['media_encryption']);
1065
1066
            $conf .= "[{$peer['extension']}-WS] \n";
1067
            $options['webrtc'] = 'yes';
1068
            $options['transport'] = 'transport-wss';
1069
            $options['aors'] = $peer['extension'] . '-WS';
1070
1071
            /** Устанавливаем кодек Opus в приоритет. */
1072
            $opusIndex = array_search('opus', $options['allow']);
1073
            if($opusIndex !== false){
1074
                unset($options['allow'][$opusIndex]);
1075
                array_unshift($options['allow'], 'opus');
1076
            }
1077
1078
            /*
1079
             * https://www.asterisk.org/rtcp-mux-webrtc/
1080
             */
1081
            $options['rtcp_mux'] = 'yes';
1082
            $conf .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
1083
            $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
1084
        }
1085
        return $conf;
1086
    }
1087
1088
}