Passed
Push — develop ( 725aa3...9b13e5 )
by Портнов
08:00
created

SIPConf::generateProviderOutAuth()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 20
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 15
c 1
b 0
f 0
dl 0
loc 20
rs 9.7666
cc 3
nc 2
nop 2
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, 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'] . 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
            $arr_data['receive_calls_without_auth'] = $sip_peer->receive_calls_without_auth;
280
            $network_filter                         = NetworkFilters::findFirst($sip_peer->networkfilterid);
281
            $arr_data['permit']                     = ($network_filter === null) ? '' : $network_filter->permit;
282
            $arr_data['deny']                       = ($network_filter === null) ? '' : $network_filter->deny;
283
284
            // Получим используемые кодеки.
285
            $arr_data['codecs'] = $this->getCodecs();
286
287
            $context_id = preg_replace("/[^a-z\d]/iu", '', $sip_peer->host . $sip_peer->port);
288
            if ( ! isset($this->contexts_data[$context_id])) {
289
                $this->contexts_data[$context_id] = [];
290
            }
291
            $this->contexts_data[$context_id][$sip_peer->uniqid] = $sip_peer->username;
292
293
            $arr_data['context_id'] = $context_id;
294
            $data[]                 = $arr_data;
295
        }
296
297
        return $data;
298
    }
299
300
    /**
301
     * Генератор исходящих контекстов для пиров.
302
     *
303
     * @return array
304
     */
305
    private function getOutRoutes(): array
306
    {
307
        if ($this->data_peers === null) {
308
            $this->getSettings();
309
        }
310
        /** @var OutgoingRoutingTable $rout */
311
        /** @var OutgoingRoutingTable $routs */
312
        /** @var Sip $db_data */
313
        /** @var Sip $sip_peer */
314
315
        $data    = [];
316
        $routs   = OutgoingRoutingTable::find(['order' => 'priority']);
317
        $db_data = Sip::find("type = 'friend' AND ( disabled <> '1')");
318
        foreach ($routs as $rout) {
319
            foreach ($db_data as $sip_peer) {
320
                if ($sip_peer->uniqid !== $rout->providerid) {
321
                    continue;
322
                }
323
                $arr_data                = $rout->toArray();
324
                $arr_data['description'] = $sip_peer->description;
325
                $arr_data['uniqid']      = $sip_peer->uniqid;
326
                $data[]                  = $arr_data;
327
            }
328
        }
329
330
        return $data;
331
    }
332
333
    /**
334
     * Returns PJSIP ot SIP uses at PBX
335
     *
336
     * @return string
337
     */
338
    public static function getTechnology(): string
339
    {
340
        return self::TYPE_PJSIP;
341
    }
342
343
    /**
344
     * Возвращает массив хостов.
345
     *
346
     * @return array
347
     */
348
    public static function getSipHosts(): array
349
    {
350
        $dataSipHosts = [];
351
        /** @var SipHosts $sipHosts */
352
        /** @var SipHosts $hostData */
353
        $sipHosts = SipHosts::find();
354
        foreach ($sipHosts as $hostData) {
355
            if ( ! isset($dataSipHosts[$hostData->provider_id])) {
356
                $dataSipHosts[$hostData->provider_id] = [];
357
            }
358
            $dataSipHosts[$hostData->provider_id][] = str_replace(PHP_EOL, '', $hostData->address);
359
        }
360
361
        return $dataSipHosts;
362
    }
363
364
    /**
365
     * Генерация хинтов.
366
     *
367
     * @return string
368
     */
369
    public function extensionGenHints(): string
370
    {
371
        if ($this->data_peers === null) {
372
            $this->getSettings();
373
        }
374
        $conf = '';
375
        foreach ($this->data_peers as $peer) {
376
            $hint = "{$this->technology}/{$peer['extension']}";
377
            if($this->generalSettings['UseWebRTC'] === '1') {
378
                $hint.="&{$this->technology}/{$peer['extension']}-WS";
379
            }
380
            $conf .= "exten => {$peer['extension']},hint,$hint&Custom:{$peer['extension']} \n";
381
        }
382
        return $conf;
383
    }
384
385
    public function extensionGenInternal(): string
386
    {
387
        if ($this->data_peers === null) {
388
            $this->getSettings();
389
        }
390
        // Генерация внутреннего номерного плана.
391
        $conf = '';
392
        foreach ($this->data_peers as $peer) {
393
            $conf .= "exten => {$peer['extension']},1,Goto(internal-users,{$peer['extension']},1) \n";
394
        }
395
        $conf .= "\n";
396
397
        return $conf;
398
    }
399
400
    public function extensionGenInternalTransfer(): string
401
    {
402
        if ($this->data_peers === null) {
403
            $this->getSettings();
404
        }
405
        // Генерация внутреннего номерного плана.
406
        $conf = '';
407
        foreach ($this->data_peers as $peer) {
408
            $conf .= "exten => {$peer['extension']},1,Set(__ISTRANSFER=transfer_) \n";
409
            $conf .= "	same => n,Goto(internal-users,{$peer['extension']},1) \n";
410
        }
411
        $conf .= "\n";
412
413
        return $conf;
414
    }
415
416
    /**
417
     * Генератор sip.conf
418
     *
419
     * @return void
420
     */
421
    protected function generateConfigProtected(): void
422
    {
423
        $conf  = $this->generateGeneralPj();
424
        $conf .= $this->generateProvidersPj();
425
        $conf .= $this->generatePeersPj();
426
427
        $astEtcDir = $this->config->path('asterisk.astetcdir');
428
429
        Util::fileWriteContent($astEtcDir . '/pjsip.conf', $conf);
430
        $pjConf = '[log_mappings]' . "\n" .
431
            'type=log_mappings' . "\n" .
432
            'asterisk_error = 0' . "\n" .
433
            'asterisk_warning = 2' . "\n" .
434
            'asterisk_debug = 1,3,4,5,6' . "\n\n";
435
        file_put_contents($astEtcDir.'/pjproject.conf', $pjConf);
436
        file_put_contents($astEtcDir.'/sorcery.conf', '');
437
        $this->updateAsteriskDatabase();
438
    }
439
440
    /**
441
     * Обновление маршрутизации в AstDB
442
     * @return bool
443
     */
444
    public function updateAsteriskDatabase():bool
445
    {
446
        if ($this->data_peers === null) {
447
            $this->getSettings();
448
        }
449
        $warError = false;
450
        $db = new AstDB();
451
        foreach ($this->data_peers as $peer) {
452
            // Помещаем в AstDB сведения по маршуртизации.
453
            $ringLength = ($peer['ringlength'] === '0') ? '' : trim($peer['ringlength']);
454
            $warError |= !$db->databasePut('FW_TIME', $peer['extension'], $ringLength);
455
            $warError |= !$db->databasePut('FW', $peer['extension'], trim($peer['forwarding']));
456
            $warError |= !$db->databasePut('FW_BUSY', $peer['extension'], trim($peer['forwardingonbusy']));
457
            $warError |= !$db->databasePut('FW_UNAV', $peer['extension'], trim($peer['forwardingonunavailable']));
458
        }
459
460
        return !$warError;
461
    }
462
463
    /**
464
     * Генератора секции general pjsip.conf
465
     *
466
     *
467
     * @return string
468
     */
469
    private function generateGeneralPj(): string
470
    {
471
        $lang = $this->generalSettings['PBXLanguage'];
472
        [$topology, $extIpAddress, $externalHostName, $subnets] = $this->getTopologyData();
473
474
        $codecs    = $this->getCodecs();
475
        $codecConf = '';
476
        foreach ($codecs as $codec) {
477
            $codecConf .= "allow = $codec\n";
478
        }
479
480
        $pbxVersion = PbxSettings::getValueByKey('PBXVersion');
481
        $natConf    = '';
482
        if ($topology === 'private') {
483
            foreach ($subnets as $net) {
484
                $natConf .= "local_net=$net\n";
485
            }
486
            if ( ! empty($externalHostName)) {
487
                $parts   = explode(':', $externalHostName);
488
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
489
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
490
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
491
            } elseif ( ! empty($extIpAddress)) {
492
                $parts   = explode(':', $extIpAddress);
493
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
494
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
495
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
496
            }
497
        }
498
499
        $typeTransport = 'type = transport';
500
        $conf = "[global] \n" .
501
            "type = global\n" .
502
            "disable_multi_domain=yes\n" .
503
            "endpoint_identifier_order=username,ip,anonymous\n" .
504
            "user_agent = mikopbx-$pbxVersion\n\n" .
505
506
            "[transport-udp]\n" .
507
            "$typeTransport\n" .
508
            "protocol = udp\n" .
509
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
510
            "$natConf\n\n" .
511
512
            "[transport-tcp]\n" .
513
            "$typeTransport\n" .
514
            "protocol = tcp\n" .
515
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
516
            "$natConf\n\n" .
517
518
            "[transport-wss]\n" .
519
            "$typeTransport\n" .
520
            "protocol = wss\n" .
521
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
522
            "$natConf\n\n";
523
524
        $allowGuestCalls = PbxSettings::getValueByKey('PBXAllowGuestCalls');
525
        if ($allowGuestCalls === '1') {
526
            $conf .= "[anonymous]\n" .
527
                "type = endpoint\n" .
528
                $codecConf .
529
                "language=$lang\n" .
530
                "timers = no\n" .
531
                "context = public-direct-dial\n\n";
532
        }
533
534
        $varEtcDir = $this->config->path('core.varEtcDir');
535
        $hash = md5($topology . $externalHostName . $extIpAddress . $this->generalSettings['SIPPort'] . implode('',$subnets));
536
        file_put_contents($varEtcDir.self::TOPOLOGY_HASH_FILE, $hash);
537
        $conf .= "\n";
538
        return $conf;
539
    }
540
541
    /**
542
     * Генератор секции провайдеров в sip.conf
543
     *
544
     *
545
     * @return string
546
     */
547
    private function generateProvidersPj(): string
548
    {
549
        $conf        = '';
550
        $reg_strings = '';
551
        $prov_config = '';
552
        if ($this->data_providers === null) {
553
            $this->getSettings();
554
        }
555
        foreach ($this->data_providers as $provider) {
556
            $manual_attributes = Util::parseIniSettings(base64_decode($provider['manualattributes'] ?? ''));
557
            $provider['port']  = (trim($provider['port']) === '') ? '5060' : $provider['port'];
558
559
            $reg_strings .= $this->generateProviderRegistrationAuth($provider, $manual_attributes);
560
            $reg_strings .= $this->generateProviderRegistration($provider, $manual_attributes);
561
            $prov_config .= $this->generateProviderOutAuth($provider, $manual_attributes);
562
563
            $prov_config .= $this->generateProviderAor($provider, $manual_attributes);
564
            $prov_config .= $this->generateProviderIdentify($provider, $manual_attributes);
565
            $prov_config .= $this->generateProviderEndpoint($provider, $manual_attributes);
566
        }
567
568
        $conf .= $reg_strings;
569
        $conf .= $prov_config;
570
571
        return $conf;
572
    }
573
574
    /**
575
     * Генерация Auth для секции Registration провайдера.
576
     *
577
     * @param array $provider
578
     * @param array $manual_attributes
579
     *
580
     * @return string
581
     */
582
    private function generateProviderRegistrationAuth(
583
        array $provider,
584
        array $manual_attributes
585
    ): string {
586
        $conf = '';
587
        if ($provider['noregister'] === '1') {
588
            return $conf;
589
        }
590
        $options         = [
591
            'type'     => 'registration-auth',
592
            'username' => $provider['username'],
593
            'password' => $provider['secret'],
594
        ];
595
        $options         = $this->overridePJSIPOptionsFromModules(
596
            $provider['uniqid'],
597
            $options,
598
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
599
        );
600
        $options['type'] = 'auth';
601
        $conf            .= "[REG-AUTH-{$provider['uniqid']}]\n";
602
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration-auth');
603
604
        return $conf;
605
    }
606
607
    /**
608
     * Calls an overridePJSIPOptions function from additional modules
609
     *
610
     * @param $extensionOrId
611
     * @param $options
612
     * @param $method
613
     *
614
     * @return array
615
     */
616
    private function overridePJSIPOptionsFromModules($extensionOrId, $options, $method): array
617
    {
618
        $configClassObj = new ConfigClass();
619
        $modulesOverridingArrays = $configClassObj->hookModulesMethodWithArrayResult($method, [$extensionOrId, $options]);
620
        foreach ($modulesOverridingArrays as $newOptionsSet) {
621
            // How to make some order of overrides?
622
            $options = $newOptionsSet;
623
        }
624
        return $options;
625
    }
626
627
    /**
628
     * Генерация Registration провайдера.
629
     *
630
     * @param array $provider
631
     * @param array $manual_attributes
632
     *
633
     * @return string
634
     */
635
    private function generateProviderRegistration(
636
        array $provider,
637
        array $manual_attributes
638
    ): string {
639
        $conf = '';
640
        if ($provider['noregister'] === '1') {
641
            return $conf;
642
        }
643
        $options = [
644
            'type'                     => 'registration',
645
            'outbound_auth'            => "REG-AUTH-{$provider['uniqid']}",
646
            'contact_user'             => $provider['username'],
647
            'retry_interval'           => '30',
648
            'max_retries'              => '100',
649
            'forbidden_retry_interval' => '300',
650
            'fatal_retry_interval'     => '300',
651
            'expiration'               => $this->generalSettings['SIPDefaultExpiry'],
652
            'server_uri'               => "sip:{$provider['host']}:{$provider['port']}",
653
            'client_uri'               => "sip:{$provider['username']}@{$provider['host']}:{$provider['port']}",
654
        ];
655
        $options = $this->overridePJSIPOptionsFromModules(
656
            $provider['uniqid'],
657
            $options,
658
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
659
        );
660
        $conf    .= "[REG-{$provider['uniqid']}] \n";
661
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration');
662
663
        return $conf;
664
    }
665
666
    /**
667
     * Генерация Auth провайдера.
668
     *
669
     * @param array $provider
670
     * @param array $manual_attributes
671
     *
672
     * @return string
673
     */
674
    private function generateProviderOutAuth(array $provider, array $manual_attributes): string {
675
        $conf = '';
676
        if ('1' === $provider['receive_calls_without_auth'] || empty("{$provider['username']}{$provider['secret']}")) {
677
            return $conf;
678
        }
679
        $options         = [
680
            'type'     => 'endpoint-auth',
681
            'username' => $provider['username'],
682
            'password' => $provider['secret'],
683
        ];
684
        $options         = $this->overridePJSIPOptionsFromModules(
685
            $provider['uniqid'],
686
            $options,
687
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
688
        );
689
        $options['type'] = 'auth';
690
        $conf            .= "[{$provider['uniqid']}-OUT]\n";
691
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint-auth');
692
693
        return $conf;
694
    }
695
696
    /**
697
     * Генерация AOR для Endpoint.
698
     *
699
     * @param array $provider
700
     * @param array $manual_attributes
701
     *
702
     * @return string
703
     */
704
    private function generateProviderAor(array $provider, array $manual_attributes): string
705
    {
706
        $conf        = '';
707
        $defaultuser = (trim($provider['defaultuser']) === '') ? $provider['username'] : $provider['defaultuser'];
708
        if ( ! empty($defaultuser) && '1' !== $provider['receive_calls_without_auth']) {
709
            $contact = "sip:$defaultuser@{$provider['host']}:{$provider['port']}";
710
        } else {
711
            $contact = "sip:{$provider['host']}:{$provider['port']}";
712
        }
713
        $options = [
714
            'type'               => 'aor',
715
            'max_contacts'       => '1',
716
            'contact'            => $contact,
717
            'maximum_expiration' => $this->generalSettings['SIPMaxExpiry'],
718
            'minimum_expiration' => $this->generalSettings['SIPMinExpiry'],
719
            'default_expiration' => $this->generalSettings['SIPDefaultExpiry'],
720
        ];
721
        if ($provider['qualify'] === '1') {
722
            $options['qualify_frequency'] = $provider['qualifyfreq'];
723
            $options['qualify_timeout']   = '3.0';
724
        }
725
        $options = $this->overridePJSIPOptionsFromModules(
726
            $provider['uniqid'],
727
            $options,
728
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
729
        );
730
        $conf    .= "[{$provider['uniqid']}]\n";
731
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
732
733
        return $conf;
734
    }
735
736
    /**
737
     * Генерация AOR для Endpoint.
738
     *
739
     * @param array $provider
740
     * @param array $manual_attributes
741
     *
742
     * @return string
743
     */
744
    private function generateProviderIdentify(
745
        array $provider,
746
        array $manual_attributes
747
    ): string {
748
        $conf          = '';
749
        $providerHosts = $this->dataSipHosts[$provider['uniqid']] ?? [];
750
        if ( ! in_array($provider['host'], $providerHosts, true)) {
751
            $providerHosts[] = $provider['host'];
752
        }
753
        $options = [
754
            'type'     => 'identify',
755
            'endpoint' => $provider['uniqid'],
756
            'match'    => implode(',', array_unique($providerHosts)),
757
        ];
758
        $options = $this->overridePJSIPOptionsFromModules(
759
            $provider['uniqid'],
760
            $options,
761
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
762
        );
763
        $conf    .= "[{$provider['uniqid']}]\n";
764
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'identify');
765
766
        return $conf;
767
    }
768
769
    /**
770
     * Генерация Endpoint провайдера.
771
     *
772
     * @param array $provider
773
     * @param array $manual_attributes
774
     *
775
     * @return string
776
     */
777
    private function generateProviderEndpoint(
778
        array $provider,
779
        array $manual_attributes
780
    ): string {
781
        $conf       = '';
782
        $fromdomain = (trim($provider['fromdomain']) === '') ? $provider['host'] : $provider['fromdomain'];
783
        $from       = (trim($provider['fromuser']) === '') ? "{$provider['username']}; username" : "{$provider['fromuser']}; fromuser";
784
785
        if($provider['disablefromuser'] === '1'){
786
            $from_user   = null;
787
            $contactUser = trim($provider['username']??'');
788
        }else{
789
            $from_user   = $from;
790
            $contactUser = $from;
791
        }
792
793
        $language   = $this->generalSettings['PBXLanguage'];
794
795
        if (count($this->contexts_data[$provider['context_id']]) === 1) {
796
            $context_id = $provider['uniqid'];
797
        } else {
798
            $context_id = $provider['context_id'];
799
        }
800
        $dtmfmode = ($provider['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $provider['dtmfmode'];
801
        $options  = [
802
            'type'            => 'endpoint',
803
            '100rel'          => "no",
804
            'context'         => "{$context_id}-incoming",
805
            'dtmf_mode'       => $dtmfmode,
806
            'disallow'        => 'all',
807
            'allow'           => $provider['codecs'],
808
            'rtp_symmetric'   => 'yes',
809
            'force_rport'     => 'yes',
810
            'rewrite_contact' => 'yes',
811
            'ice_support'     => 'no',
812
            'direct_media'    => 'no',
813
            'from_user'       => $from_user,
814
            'from_domain'     => $fromdomain,
815
            'contact_user'    => $contactUser,
816
            'sdp_session'     => 'mikopbx',
817
            'language'        => $language,
818
            'aors'            => $provider['uniqid'],
819
            'timers'          => ' no',
820
        ];
821
        if ('1' !== $provider['receive_calls_without_auth'] && !empty("{$provider['username']}{$provider['secret']}")) {
822
            $options['outbound_auth'] = "{$provider['uniqid']}-OUT";
823
        }
824
        self::getToneZone($options, $language);
825
        $options = $this->overridePJSIPOptionsFromModules(
826
            $provider['uniqid'],
827
            $options,
828
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
829
        );
830
        $conf    .= "[{$provider['uniqid']}]\n";
831
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
832
833
        return $conf;
834
    }
835
836
    /**
837
     * @param array  $options
838
     * @param string $lang
839
     */
840
    public static function getToneZone(array &$options, string $lang): void
841
    {
842
        $settings = [
843
            'ru-ru' => 'ru',
844
            'en-gb' => 'uk',
845
            'de-de' => 'de',
846
            'da-dk' => 'dk',
847
            'es-es' => 'es',
848
            'fr-ca' => 'fr',
849
            'it-it' => 'it',
850
            'ja-jp' => 'jp',
851
            'nl-nl' => 'nl',
852
            'pl-pl' => 'pl',
853
            'pt-br' => 'pt',
854
        ];
855
        $toneZone = $settings[$lang] ?? '';
856
        if ( ! empty($toneZone)) {
857
            $options['inband_progress'] = 'yes';
858
            $options['tone_zone']       = $toneZone;
859
        }
860
    }
861
862
    /**
863
     * Генератор сеции пиров для sip.conf
864
     *
865
     *
866
     * @return string
867
     */
868
    public function generatePeersPj(): string
869
    {
870
        if ($this->data_peers === null) {
871
            $this->getSettings();
872
        }
873
        $lang = $this->generalSettings['PBXLanguage'];
874
        $conf = '';
875
876
        foreach ($this->data_peers as $peer) {
877
            $manual_attributes = Util::parseIniSettings($peer['manualattributes'] ?? '');
878
            $conf              .= $this->generatePeerAuth($peer, $manual_attributes);
879
            $conf              .= $this->generatePeerAor($peer, $manual_attributes);
880
            $conf              .= $this->generatePeerEndpoint($lang, $peer, $manual_attributes);
881
        }
882
883
        $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEERS_PJ);
884
885
        return $conf;
886
    }
887
888
    /**
889
     * Генерация AOR для Endpoint.
890
     *
891
     * @param array $peer
892
     * @param array $manual_attributes
893
     *
894
     * @return string
895
     */
896
    private function generatePeerAuth(array $peer, array $manual_attributes): string
897
    {
898
        $conf    = '';
899
        $options = [
900
            'type'     => 'auth',
901
            'username' => $peer['extension'],
902
            'password' => $peer['secret'],
903
        ];
904
        $options = $this->overridePJSIPOptionsFromModules(
905
            $peer['extension'],
906
            $options,
907
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
908
        );
909
        $conf    .= "[{$peer['extension']}] \n";
910
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'auth');
911
912
        return $conf;
913
    }
914
915
    /**
916
     * Генерация AOR для Endpoint.
917
     *
918
     * @param array $peer
919
     * @param array $manual_attributes
920
     *
921
     * @return string
922
     */
923
    private function generatePeerAor(array $peer, array $manual_attributes): string
924
    {
925
        $conf    = '';
926
        $options = [
927
            'type'              => 'aor',
928
            'qualify_frequency' => '60',
929
            'qualify_timeout'   => '5',
930
            'max_contacts'      => '5',
931
        ];
932
        $options = $this->overridePJSIPOptionsFromModules(
933
            $peer['extension'],
934
            $options,
935
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
936
        );
937
        $conf    .= "[{$peer['extension']}]\n";
938
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
939
940
        if($this->generalSettings['UseWebRTC'] === '1'){
941
            $conf    .= "[{$peer['extension']}-WS]\n";
942
            $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
943
        }
944
945
        return $conf;
946
    }
947
948
    /**
949
     * Генерация endpoint.
950
     *
951
     * @param        $lang
952
     * @param array  $peer
953
     * @param array  $manual_attributes
954
     *
955
     * @return string
956
     */
957
    private function generatePeerEndpoint(
958
        $lang,
959
        array $peer,
960
        array $manual_attributes
961
    ): string {
962
        $conf     = '';
963
        $language = str_replace('_', '-', strtolower($lang));
964
        $language = (trim($language) === '') ? 'en-en' : $language;
965
966
        $calleridname = (trim($peer['calleridname']) === '') ? $peer['extension'] : $peer['calleridname'];
967
        $busylevel    = (trim($peer['busylevel']) === '') ? '1' : '' . $peer['busylevel'];
968
969
        $dtmfmode = ($peer['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $peer['dtmfmode'];
970
        $options  = [
971
            'type'                 => 'endpoint',
972
            'context'              => 'all_peers',
973
            'dtmf_mode'            => $dtmfmode,
974
            'disallow'             => 'all',
975
            'allow'                => $peer['codecs'],
976
            'rtp_symmetric'        => 'yes',
977
            'force_rport'          => 'yes',
978
            'rewrite_contact'      => 'yes',
979
            'ice_support'          => 'no',
980
            'direct_media'         => 'no',
981
            'callerid'             => "{$calleridname} <{$peer['extension']}>",
982
            'send_pai'             => 'yes',
983
            'call_group'           => '1',
984
            'pickup_group'         => '1',
985
            'sdp_session'          => 'mikopbx',
986
            'language'             => $language,
987
            'mailboxes'            => 'admin@voicemailcontext',
988
            'device_state_busy_at' => $busylevel,
989
            'aors'                 => $peer['extension'],
990
            'auth'                 => $peer['extension'],
991
            'outbound_auth'        => $peer['extension'],
992
            'acl'                  => "acl_{$peer['extension']}",
993
            'timers'               => 'no',
994
            'message_context'      => 'messages',
995
        ];
996
        self::getToneZone($options, $language);
997
        $options = $this->overridePJSIPOptionsFromModules(
998
            $peer['extension'],
999
            $options,
1000
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
1001
        );
1002
        $conf    .= "[{$peer['extension']}] \n";
1003
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
1004
        $conf    .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
1005
1006
        if($this->generalSettings['UseWebRTC'] === '1') {
1007
            $conf .= "[{$peer['extension']}-WS] \n";
1008
            $options['webrtc'] = 'yes';
1009
            $options['transport'] = 'transport-wss';
1010
            $options['aors'] = $peer['extension'] . '-WS';
1011
1012
            /** Устанавливаем кодек Opus в приоритет. */
1013
            $opusIndex = array_search('opus', $options['allow']);
1014
            if($opusIndex !== false){
1015
                unset($options['allow'][$opusIndex]);
1016
                array_unshift($options['allow'], 'opus');
1017
            }
1018
1019
            /*
1020
             * https://www.asterisk.org/rtcp-mux-webrtc/
1021
             */
1022
            $options['rtcp_mux'] = 'yes';
1023
            $conf .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
1024
            $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
1025
        }
1026
        return $conf;
1027
    }
1028
1029
}