Passed
Push — develop ( dcb728...9b177f )
by Портнов
05:21
created

SIPConf::extensionGenContexts()   B

Complexity

Conditions 9
Paths 64

Size

Total Lines 46
Code Lines 30

Duplication

Lines 0
Ratio 0 %

Importance

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