Passed
Push — develop ( d66898...d52602 )
by Nikolay
12:48
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, 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
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, $extipaddr, $exthostname] = $this->getTopologyData();
77
78
        $generalSettings = $mikoPBXConfig->getGeneralSettings();
79
        $now_hadh        = md5($topology . $exthostname . $extipaddr . $generalSettings['SIPPort']);
80
        $old_hash        = '';
81
        $varEtcDir       = $di->getShared('config')->path('core.varEtcDir');
82
        if (file_exists($varEtcDir . '/topology_hash')) {
83
            $old_hash = file_get_contents($varEtcDir . '/topology_hash');
84
        }
85
86
        return $old_hash !== $now_hadh;
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     = [];
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
            $contexts_data = $this->contexts_data[$provider['context_id']];
155
            if (count($contexts_data) === 1) {
156
                $conf .= IncomingContexts::generate($provider['uniqid'], $provider['username']);
157
            } elseif ( ! in_array($provider['context_id'], $contexts, true)) {
158
                $conf       .= IncomingContexts::generate(
159
                    $contexts_data,
160
                    null,
161
                    $provider['context_id']
162
                );
163
                $contexts[] = $provider['context_id'];
164
            }
165
        }
166
167
        return $conf;
168
    }
169
170
    /**
171
     * Получение настроек.
172
     */
173
    public function getSettings(): void
174
    {
175
        $this->contexts_data = [];
176
        // Настройки для текущего класса.
177
        $this->data_peers        = $this->getPeers();
178
        $this->data_providers    = $this->getProviders();
179
        $this->data_rout         = $this->getOutRoutes();
180
        $this->technology        = self::getTechnology();
181
        $this->dataSipHosts      = self::getSipHosts();
182
    }
183
184
    /**
185
     * Получение данных по SIP пирам.
186
     *
187
     * @return array
188
     */
189
    private function getPeers(): array
190
    {
191
        /** @var NetworkFilters $network_filter */
192
        /** @var Sip $sip_peer */
193
        /** @var Extensions $extension */
194
        /** @var Users $user */
195
        /** @var ExtensionForwardingRights $extensionForwarding */
196
197
        $data    = [];
198
        $db_data = Sip::find("type = 'peer' AND ( disabled <> '1')");
199
        foreach ($db_data as $sip_peer) {
200
            $arr_data       = $sip_peer->toArray();
201
            $network_filter = null;
202
            if ( ! empty($sip_peer->networkfilterid)) {
203
                $network_filter = NetworkFilters::findFirst($sip_peer->networkfilterid);
204
            }
205
            $arr_data['permit'] = ($network_filter === null) ? '' : $network_filter->permit;
206
            $arr_data['deny']   = ($network_filter === null) ? '' : $network_filter->deny;
207
208
            // Получим используемые кодеки.
209
            $arr_data['codecs'] = $this->getCodecs();
210
211
            // Имя сотрудника.
212
            $extension = Extensions::findFirst("number = '{$sip_peer->extension}'");
213
            if (null === $extension) {
214
                $arr_data['publicaccess'] = false;
215
                $arr_data['language']     = '';
216
                $arr_data['calleridname'] = $sip_peer->extension;
217
            } else {
218
                $arr_data['publicaccess'] = $extension->public_access;
219
                $arr_data['calleridname'] = $extension->callerid;
220
                $user                     = Users::findFirst($extension->userid);
221
                if (null !== $user) {
222
                    $arr_data['language'] = $user->language;
223
                    $arr_data['user_id']  = $user->id;
224
                }
225
            }
226
            $extensionForwarding = ExtensionForwardingRights::findFirst("extension = '{$sip_peer->extension}'");
227
            if (null === $extensionForwarding) {
228
                $arr_data['ringlength']              = '';
229
                $arr_data['forwarding']              = '';
230
                $arr_data['forwardingonbusy']        = '';
231
                $arr_data['forwardingonunavailable'] = '';
232
            } else {
233
                $arr_data['ringlength']              = $extensionForwarding->ringlength;
234
                $arr_data['forwarding']              = $extensionForwarding->forwarding;
235
                $arr_data['forwardingonbusy']        = $extensionForwarding->forwardingonbusy;
236
                $arr_data['forwardingonunavailable'] = $extensionForwarding->forwardingonunavailable;
237
            }
238
            $data[] = $arr_data;
239
        }
240
241
        return $data;
242
    }
243
244
    /**
245
     * Возвращает доступные пиру кодеки.
246
     *
247
     * @return array
248
     */
249
    private function getCodecs(): array
250
    {
251
        $arr_codecs = [];
252
        $filter     = [
253
            'conditions' => 'disabled="0"',
254
            'order'      => 'type, priority',
255
        ];
256
        $codecs     = Codecs::find($filter);
257
        foreach ($codecs as $codec_data) {
258
            $arr_codecs[] = $codec_data->name;
259
        }
260
261
        return $arr_codecs;
262
    }
263
264
    /**
265
     * Получение данных по SIP провайдерам.
266
     *
267
     * @return array
268
     */
269
    private function getProviders(): array
270
    {
271
        /** @var Sip $sip_peer */
272
        /** @var NetworkFilters $network_filter */
273
        // Получим настройки всех аккаунтов.
274
        $data    = [];
275
        $db_data = Sip::find("type = 'friend' AND ( disabled <> '1')");
276
        foreach ($db_data as $sip_peer) {
277
            $arr_data                               = $sip_peer->toArray();
278
            $arr_data['receive_calls_without_auth'] = $sip_peer->receive_calls_without_auth;
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
            // Получим используемые кодеки.
284
            $arr_data['codecs'] = $this->getCodecs();
285
286
            $context_id = preg_replace("/[^a-z\d]/iu", '', $sip_peer->host . $sip_peer->port);
287
            if ( ! isset($this->contexts_data[$context_id])) {
288
                $this->contexts_data[$context_id] = [];
289
            }
290
            $this->contexts_data[$context_id][$sip_peer->uniqid] = $sip_peer->username;
291
292
            $arr_data['context_id'] = $context_id;
293
            $data[]                 = $arr_data;
294
        }
295
296
        return $data;
297
    }
298
299
    /**
300
     * Генератор исходящих контекстов для пиров.
301
     *
302
     * @return array
303
     */
304
    private function getOutRoutes(): array
305
    {
306
        if ($this->data_peers === null) {
307
            $this->getSettings();
308
        }
309
        /** @var OutgoingRoutingTable $rout */
310
        /** @var OutgoingRoutingTable $routs */
311
        /** @var Sip $db_data */
312
        /** @var Sip $sip_peer */
313
314
        $data    = [];
315
        $routs   = OutgoingRoutingTable::find(['order' => 'priority']);
316
        $db_data = Sip::find("type = 'friend' AND ( disabled <> '1')");
317
        foreach ($routs as $rout) {
318
            foreach ($db_data as $sip_peer) {
319
                if ($sip_peer->uniqid !== $rout->providerid) {
320
                    continue;
321
                }
322
                $arr_data                = $rout->toArray();
323
                $arr_data['description'] = $sip_peer->description;
324
                $arr_data['uniqid']      = $sip_peer->uniqid;
325
                $data[]                  = $arr_data;
326
            }
327
        }
328
329
        return $data;
330
    }
331
332
    /**
333
     * Returns PJSIP ot SIP uses at PBX
334
     *
335
     * @return string
336
     */
337
    public static function getTechnology(): string
338
    {
339
        return self::TYPE_PJSIP;
340
    }
341
342
    /**
343
     * Возвращает массив хостов.
344
     *
345
     * @return array
346
     */
347
    public static function getSipHosts(): array
348
    {
349
        $dataSipHosts = [];
350
        /** @var SipHosts $sipHosts */
351
        /** @var SipHosts $hostData */
352
        $sipHosts = SipHosts::find();
353
        foreach ($sipHosts as $hostData) {
354
            if ( ! isset($dataSipHosts[$hostData->provider_id])) {
355
                $dataSipHosts[$hostData->provider_id] = [];
356
            }
357
            $dataSipHosts[$hostData->provider_id][] = str_replace(PHP_EOL, '', $hostData->address);
358
        }
359
360
        return $dataSipHosts;
361
    }
362
363
    /**
364
     * Генерация хинтов.
365
     *
366
     * @return string
367
     */
368
    public function extensionGenHints(): string
369
    {
370
        if ($this->data_peers === null) {
371
            $this->getSettings();
372
        }
373
        $conf = '';
374
        foreach ($this->data_peers as $peer) {
375
            $conf .= "exten => {$peer['extension']},hint,{$this->technology}/{$peer['extension']}&Custom:{$peer['extension']} \n";
376
        }
377
378
        return $conf;
379
    }
380
381
    public function extensionGenInternal(): string
382
    {
383
        if ($this->data_peers === null) {
384
            $this->getSettings();
385
        }
386
        // Генерация внутреннего номерного плана.
387
        $conf = '';
388
        foreach ($this->data_peers as $peer) {
389
            $conf .= "exten => {$peer['extension']},1,Goto(internal-users,{$peer['extension']},1) \n";
390
        }
391
        $conf .= "\n";
392
393
        return $conf;
394
    }
395
396
    public function extensionGenInternalTransfer(): string
397
    {
398
        if ($this->data_peers === null) {
399
            $this->getSettings();
400
        }
401
        // Генерация внутреннего номерного плана.
402
        $conf = '';
403
        foreach ($this->data_peers as $peer) {
404
            $conf .= "exten => {$peer['extension']},1,Set(__ISTRANSFER=transfer_) \n";
405
            $conf .= "	same => n,Goto(internal-users,{$peer['extension']},1) \n";
406
        }
407
        $conf .= "\n";
408
409
        return $conf;
410
    }
411
412
    /**
413
     * Генератор sip.conf
414
     *
415
     * @return bool|void
416
     */
417
    protected function generateConfigProtected(): void
418
    {
419
        $conf = '';
420
        $conf .= $this->generateGeneralPj();
421
        $conf .= $this->generateProvidersPj();
422
        $conf .= $this->generatePeersPj();
423
424
        Util::fileWriteContent($this->config->path('asterisk.astetcdir') . '/pjsip.conf', $conf);
425
        $pjConf = '[log_mappings]' . "\n" .
426
            'type=log_mappings' . "\n" .
427
            'asterisk_error = 0' . "\n" .
428
            'asterisk_warning = 2' . "\n" .
429
            'asterisk_debug = 1,3,4,5,6' . "\n\n";
430
431
        file_put_contents($this->config->path('asterisk.astetcdir') . '/pjproject.conf', $pjConf);
432
        file_put_contents($this->config->path('asterisk.astetcdir') . '/sorcery.conf', '');
433
434
        $db = new AstDB();
435
        foreach ($this->data_peers as $peer) {
436
            // Помещаем в AstDB сведения по маршуртизации.
437
            $ringlength = ($peer['ringlength'] === '0') ? '' : trim($peer['ringlength']);
438
            $db->databasePut('FW_TIME', $peer['extension'], $ringlength);
439
            $db->databasePut('FW', $peer['extension'], trim($peer['forwarding']));
440
            $db->databasePut('FW_BUSY', $peer['extension'], trim($peer['forwardingonbusy']));
441
            $db->databasePut('FW_UNAV', $peer['extension'], trim($peer['forwardingonunavailable']));
442
        }
443
    }
444
445
    /**
446
     * Генератора секции general pjsip.conf
447
     *
448
     *
449
     * @return string
450
     */
451
    private function generateGeneralPj(): string
452
    {
453
        $lang = $this->generalSettings['PBXLanguage'];
454
        [$topology, $extipaddr, $exthostname, $subnets] = $this->getTopologyData();
455
456
        $codecs    = $this->getCodecs();
457
        $codecConf = '';
458
        foreach ($codecs as $codec) {
459
            $codecConf .= "allow = {$codec}\n";
460
        }
461
462
        $pbxVersion = PbxSettings::getValueByKey('PBXVersion');
463
        $natConf    = '';
464
        if ($topology === 'private') {
465
            foreach ($subnets as $net) {
466
                $natConf .= "local_net={$net}\n";
467
            }
468
            if ( ! empty($exthostname)) {
469
                $parts   = explode(':', $exthostname);
470
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
471
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
472
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
473
            } elseif ( ! empty($extipaddr)) {
474
                $parts   = explode(':', $extipaddr);
475
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
476
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
477
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
478
            }
479
        }
480
481
        $conf = "[general] \n" .
482
            "disable_multi_domain=on\n" .
483
            "transport = udp \n\n" .
484
485
            "[global] \n" .
486
            "type = global\n" .
487
            "endpoint_identifier_order=username,ip,anonymous\n" .
488
            "user_agent = mikopbx-{$pbxVersion}\n\n" .
489
490
            "[transport-udp]\n" .
491
            "type = transport\n" .
492
            "protocol = udp\n" .
493
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
494
            "{$natConf}\n\n" .
495
496
            "[transport-tcp]\n" .
497
            "type = transport\n" .
498
            "protocol = tcp\n" .
499
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
500
            "{$natConf}\n\n" .
501
            '';
502
503
        $allowGuestCalls = PbxSettings::getValueByKey('PBXAllowGuestCalls');
504
        if ($allowGuestCalls === '1') {
505
            $conf .= "[anonymous]\n" .
506
                "type = endpoint\n" .
507
                $codecConf .
508
                "language={$lang}\n" .
509
                "timers = no\n" .
510
                "context = public-direct-dial\n\n";
511
        }
512
513
        $varEtcDir = $this->config->path('core.varEtcDir');
514
        file_put_contents(
515
            $varEtcDir . '/topology_hash',
516
            md5($topology . $exthostname . $extipaddr . $this->generalSettings['SIPPort'])
517
        );
518
        $conf .= "\n";
519
520
        return $conf;
521
    }
522
523
    /**
524
     * Генератор секции провайдеров в sip.conf
525
     *
526
     *
527
     * @return string
528
     */
529
    private function generateProvidersPj(): string
530
    {
531
        $conf        = '';
532
        $reg_strings = '';
533
        $prov_config = '';
534
        if ($this->data_providers === null) {
535
            $this->getSettings();
536
        }
537
        foreach ($this->data_providers as $provider) {
538
            $manual_attributes = Util::parseIniSettings(base64_decode($provider['manualattributes'] ?? ''));
539
            $provider['port']  = (trim($provider['port']) === '') ? '5060' : $provider['port'];
540
541
            $reg_strings .= $this->generateProviderRegistrationAuth($provider, $manual_attributes);
542
            $reg_strings .= $this->generateProviderRegistration($provider, $manual_attributes);
543
            $prov_config .= $this->generateProviderOutAuth($provider, $manual_attributes);
544
545
            $prov_config .= $this->generateProviderAor($provider, $manual_attributes);
546
            $prov_config .= $this->generateProviderIdentify($provider, $manual_attributes);
547
            $prov_config .= $this->generateProviderEndpoint($provider, $manual_attributes);
548
        }
549
550
        $conf .= $reg_strings;
551
        $conf .= $prov_config;
552
553
        return $conf;
554
    }
555
556
    /**
557
     * Генерация Auth для секции Registration провайдера.
558
     *
559
     * @param array $provider
560
     * @param array $manual_attributes
561
     *
562
     * @return string
563
     */
564
    private function generateProviderRegistrationAuth(
565
        array $provider,
566
        array $manual_attributes
567
    ): string {
568
        $conf = '';
569
        if ($provider['noregister'] === '1') {
570
            return $conf;
571
        }
572
        $options         = [
573
            'type'     => 'registration-auth',
574
            'username' => $provider['username'],
575
            'password' => $provider['secret'],
576
        ];
577
        $options         = $this->overridePJSIPOptionsFromModules(
578
            $provider['uniqid'],
579
            $options,
580
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
581
        );
582
        $options['type'] = 'auth';
583
        $conf            .= "[REG-AUTH-{$provider['uniqid']}]\n";
584
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration-auth');
585
586
        return $conf;
587
    }
588
589
    /**
590
     * Calls an overridePJSIPOptions function from additional modules
591
     *
592
     * @param $extensionOrId
593
     * @param $options
594
     * @param $method
595
     *
596
     * @return array
597
     */
598
    private function overridePJSIPOptionsFromModules($extensionOrId, $options, $method): array
599
    {
600
        $configClassObj = new ConfigClass();
601
        $modulesOverridingArrays = $configClassObj->hookModulesMethodWithArrayResult($method, [$extensionOrId, $options]);
602
        foreach ($modulesOverridingArrays as $newOptionsSet) {
603
            // How to make some order of overrides?
604
            $options = $newOptionsSet;
605
        }
606
        return $options;
607
    }
608
609
    /**
610
     * Генерация Registration провайдера.
611
     *
612
     * @param array $provider
613
     * @param array $manual_attributes
614
     *
615
     * @return string
616
     */
617
    private function generateProviderRegistration(
618
        array $provider,
619
        array $manual_attributes
620
    ): string {
621
        $conf = '';
622
        if ($provider['noregister'] === '1') {
623
            return $conf;
624
        }
625
        $options = [
626
            'type'                     => 'registration',
627
            'outbound_auth'            => "REG-AUTH-{$provider['uniqid']}",
628
            'contact_user'             => $provider['username'],
629
            'retry_interval'           => '30',
630
            'max_retries'              => '100',
631
            'forbidden_retry_interval' => '300',
632
            'fatal_retry_interval'     => '300',
633
            'expiration'               => $this->generalSettings['SIPDefaultExpiry'],
634
            'server_uri'               => "sip:{$provider['host']}:{$provider['port']}",
635
            'client_uri'               => "sip:{$provider['username']}@{$provider['host']}:{$provider['port']}",
636
        ];
637
        $options = $this->overridePJSIPOptionsFromModules(
638
            $provider['uniqid'],
639
            $options,
640
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
641
        );
642
        $conf    .= "[REG-{$provider['uniqid']}] \n";
643
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration');
644
645
        return $conf;
646
    }
647
648
    /**
649
     * Генерация Auth провайдера.
650
     *
651
     * @param array $provider
652
     * @param array $manual_attributes
653
     *
654
     * @return string
655
     */
656
    private function generateProviderOutAuth(
657
        array $provider,
658
        array $manual_attributes
659
    ): string {
660
        $conf = '';
661
        if ('1' === $provider['receive_calls_without_auth']) {
662
            return $conf;
663
        }
664
        $options         = [
665
            'type'     => 'endpoint-auth',
666
            'username' => $provider['username'],
667
            'password' => $provider['secret'],
668
        ];
669
        $options         = $this->overridePJSIPOptionsFromModules(
670
            $provider['uniqid'],
671
            $options,
672
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
673
        );
674
        $options['type'] = 'auth';
675
        $conf            .= "[{$provider['uniqid']}-OUT]\n";
676
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint-auth');
677
678
        return $conf;
679
    }
680
681
    /**
682
     * Генерация AOR для Endpoint.
683
     *
684
     * @param array $provider
685
     * @param array $manual_attributes
686
     *
687
     * @return string
688
     */
689
    private function generateProviderAor(array $provider, array $manual_attributes): string
690
    {
691
        $conf        = '';
692
        $defaultuser = (trim($provider['defaultuser']) === '') ? $provider['username'] : $provider['defaultuser'];
693
        if ( ! empty($defaultuser) && '1' !== $provider['receive_calls_without_auth']) {
694
            $contact = "sip:$defaultuser@{$provider['host']}:{$provider['port']}";
695
        } else {
696
            $contact = "sip:{$provider['host']}:{$provider['port']}";
697
        }
698
        $options = [
699
            'type'               => 'aor',
700
            'max_contacts'       => '1',
701
            'contact'            => $contact,
702
            'maximum_expiration' => $this->generalSettings['SIPMaxExpiry'],
703
            'minimum_expiration' => $this->generalSettings['SIPMinExpiry'],
704
            'default_expiration' => $this->generalSettings['SIPDefaultExpiry'],
705
        ];
706
        if ($provider['qualify'] === '1') {
707
            $options['qualify_frequency'] = $provider['qualifyfreq'];
708
            $options['qualify_timeout']   = '3.0';
709
        }
710
        $options = $this->overridePJSIPOptionsFromModules(
711
            $provider['uniqid'],
712
            $options,
713
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
714
        );
715
        $conf    .= "[{$provider['uniqid']}]\n";
716
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
717
718
        return $conf;
719
    }
720
721
    /**
722
     * Генерация AOR для Endpoint.
723
     *
724
     * @param array $provider
725
     * @param array $manual_attributes
726
     *
727
     * @return string
728
     */
729
    private function generateProviderIdentify(
730
        array $provider,
731
        array $manual_attributes
732
    ): string {
733
        $conf          = '';
734
        $providerHosts = $this->dataSipHosts[$provider['uniqid']] ?? [];
735
        if ( ! in_array($provider['host'], $providerHosts, true)) {
736
            $providerHosts[] = $provider['host'];
737
        }
738
        $options = [
739
            'type'     => 'identify',
740
            'endpoint' => $provider['uniqid'],
741
            'match'    => implode(',', array_unique($providerHosts)),
742
        ];
743
        $options = $this->overridePJSIPOptionsFromModules(
744
            $provider['uniqid'],
745
            $options,
746
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
747
        );
748
        $conf    .= "[{$provider['uniqid']}]\n";
749
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'identify');
750
751
        return $conf;
752
    }
753
754
    /**
755
     * Генерация Endpoint провайдера.
756
     *
757
     * @param array $provider
758
     * @param array $manual_attributes
759
     *
760
     * @return string
761
     */
762
    private function generateProviderEndpoint(
763
        array $provider,
764
        array $manual_attributes
765
    ): string {
766
        $conf       = '';
767
        $fromdomain = (trim($provider['fromdomain']) === '') ? $provider['host'] : $provider['fromdomain'];
768
        $from       = (trim(
769
                $provider['fromuser']
770
            ) === '') ? "{$provider['username']}; username" : "{$provider['fromuser']}; fromuser";
771
        $from_user  = ($provider['disablefromuser'] === '1') ? null : $from;
772
        $language   = $this->generalSettings['PBXLanguage'];
773
774
        if (count($this->contexts_data[$provider['context_id']]) === 1) {
775
            $context_id = $provider['uniqid'];
776
        } else {
777
            $context_id = $provider['context_id'];
778
        }
779
        $dtmfmode = ($provider['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $provider['dtmfmode'];
780
        $options  = [
781
            'type'            => 'endpoint',
782
            '100rel'          => "no",
783
            'context'         => "{$context_id}-incoming",
784
            'dtmf_mode'       => $dtmfmode,
785
            'disallow'        => 'all',
786
            'allow'           => $provider['codecs'],
787
            'rtp_symmetric'   => 'yes',
788
            'force_rport'     => 'yes',
789
            'rewrite_contact' => 'yes',
790
            'ice_support'     => 'no',
791
            'direct_media'    => 'no',
792
            'from_user'       => $from_user,
793
            'from_domain'     => $fromdomain,
794
            'sdp_session'     => 'mikopbx',
795
            'language'        => $language,
796
            'aors'            => $provider['uniqid'],
797
            'timers'          => ' no',
798
        ];
799
        if ('1' !== $provider['receive_calls_without_auth']) {
800
            $options['outbound_auth'] = "{$provider['uniqid']}-OUT";
801
        }
802
        self::getToneZone($options, $language);
803
        $options = $this->overridePJSIPOptionsFromModules(
804
            $provider['uniqid'],
805
            $options,
806
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
807
        );
808
        $conf    .= "[{$provider['uniqid']}]\n";
809
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
810
811
        return $conf;
812
    }
813
814
    /**
815
     * @param array  $options
816
     * @param string $lang
817
     */
818
    public static function getToneZone(array &$options, string $lang): void
819
    {
820
        $settings = [
821
            'ru-ru' => 'ru',
822
            'en-gb' => 'uk',
823
            'de-de' => 'de',
824
            'da-dk' => 'dk',
825
            'es-es' => 'es',
826
            'fr-ca' => 'fr',
827
            'it-it' => 'it',
828
            'ja-jp' => 'jp',
829
            'nl-nl' => 'nl',
830
            'pl-pl' => 'pl',
831
            'pt-br' => 'pt',
832
        ];
833
        $toneZone = $settings[$lang] ?? '';
834
        if ( ! empty($toneZone)) {
835
            $options['inband_progress'] = 'yes';
836
            $options['tone_zone']       = $toneZone;
837
        }
838
    }
839
840
    /**
841
     * Генератор сеции пиров для sip.conf
842
     *
843
     *
844
     * @return string
845
     */
846
    public function generatePeersPj(): string
847
    {
848
        if ($this->data_peers === null) {
849
            $this->getSettings();
850
        }
851
        $lang = $this->generalSettings['PBXLanguage'];
852
        $conf = '';
853
854
        foreach ($this->data_peers as $peer) {
855
            $manual_attributes = Util::parseIniSettings($peer['manualattributes'] ?? '');
856
            $conf              .= $this->generatePeerAuth($peer, $manual_attributes);
857
            $conf              .= $this->generatePeerAor($peer, $manual_attributes);
858
            $conf              .= $this->generatePeerEndpoint($lang, $peer, $manual_attributes);
859
        }
860
861
        $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEERS_PJ);
862
863
        return $conf;
864
    }
865
866
    /**
867
     * Генерация AOR для Endpoint.
868
     *
869
     * @param array $peer
870
     * @param array $manual_attributes
871
     *
872
     * @return string
873
     */
874
    private function generatePeerAuth(array $peer, array $manual_attributes): string
875
    {
876
        $conf    = '';
877
        $options = [
878
            'type'     => 'auth',
879
            'username' => $peer['extension'],
880
            'password' => $peer['secret'],
881
        ];
882
        $options = $this->overridePJSIPOptionsFromModules(
883
            $peer['extension'],
884
            $options,
885
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
886
        );
887
        $conf    .= "[{$peer['extension']}] \n";
888
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'auth');
889
890
        return $conf;
891
    }
892
893
    /**
894
     * Генерация AOR для Endpoint.
895
     *
896
     * @param array $peer
897
     * @param array $manual_attributes
898
     *
899
     * @return string
900
     */
901
    private function generatePeerAor(array $peer, array $manual_attributes): string
902
    {
903
        $conf    = '';
904
        $options = [
905
            'type'              => 'aor',
906
            'qualify_frequency' => '60',
907
            'qualify_timeout'   => '5',
908
            'max_contacts'      => '5',
909
        ];
910
        $options = $this->overridePJSIPOptionsFromModules(
911
            $peer['extension'],
912
            $options,
913
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
914
        );
915
        $conf    .= "[{$peer['extension']}] \n";
916
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
917
918
        return $conf;
919
    }
920
921
    /**
922
     * Генерация endpoint.
923
     *
924
     * @param        $lang
925
     * @param array  $peer
926
     * @param array  $manual_attributes
927
     *
928
     * @return string
929
     */
930
    private function generatePeerEndpoint(
931
        $lang,
932
        array $peer,
933
        array $manual_attributes
934
    ): string {
935
        $conf     = '';
936
        $language = str_replace('_', '-', strtolower($lang));
937
        $language = (trim($language) === '') ? 'en-en' : $language;
938
939
        $calleridname = (trim($peer['calleridname']) === '') ? $peer['extension'] : $peer['calleridname'];
940
        $busylevel    = (trim($peer['busylevel']) === '') ? '1' : '' . $peer['busylevel'];
941
942
        $dtmfmode = ($peer['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $peer['dtmfmode'];
943
        $options  = [
944
            'type'                 => 'endpoint',
945
            'context'              => 'all_peers',
946
            'dtmf_mode'            => $dtmfmode,
947
            'disallow'             => 'all',
948
            'allow'                => $peer['codecs'],
949
            'rtp_symmetric'        => 'yes',
950
            'force_rport'          => 'yes',
951
            'rewrite_contact'      => 'yes',
952
            'ice_support'          => 'no',
953
            'direct_media'         => 'no',
954
            'callerid'             => "{$calleridname} <{$peer['extension']}>",
955
            'send_pai'             => 'yes',
956
            'call_group'           => '1',
957
            'pickup_group'         => '1',
958
            'sdp_session'          => 'mikopbx',
959
            'language'             => $language,
960
            'mailboxes'            => 'admin@voicemailcontext',
961
            'device_state_busy_at' => $busylevel,
962
            'aors'                 => $peer['extension'],
963
            'auth'                 => $peer['extension'],
964
            'outbound_auth'        => $peer['extension'],
965
            'acl'                  => "acl_{$peer['extension']}",
966
            'timers'               => ' no',
967
        ];
968
        self::getToneZone($options, $language);
969
        $options = $this->overridePJSIPOptionsFromModules(
970
            $peer['extension'],
971
            $options,
972
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
973
        );
974
        $conf    .= "[{$peer['extension']}] \n";
975
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
976
        $conf    .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
977
978
        return $conf;
979
    }
980
981
}