Passed
Push — develop ( 06fb87...d66898 )
by Nikolay
23:43
created

SIPConf::generateProviderEndpoint()   B

Complexity

Conditions 7
Paths 64

Size

Total Lines 50
Code Lines 40

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 40
dl 0
loc 50
rs 8.3466
c 0
b 0
f 0
cc 7
nc 64
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\Common\Providers\PBXConfModulesProvider;
33
use MikoPBX\Core\Asterisk\AstDB;
34
use MikoPBX\Core\Asterisk\Configs\Generators\Extensions\IncomingContexts;
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 dependenceModels(): 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
        $this->additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
0 ignored issues
show
Bug Best Practice introduced by
The property additionalModules does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
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
            $conf .= "exten => {$peer['extension']},hint,{$this->technology}/{$peer['extension']}&Custom:{$peer['extension']} \n";
377
        }
378
379
        return $conf;
380
    }
381
382
    public function extensionGenInternal(): string
383
    {
384
        if ($this->data_peers === null) {
385
            $this->getSettings();
386
        }
387
        // Генерация внутреннего номерного плана.
388
        $conf = '';
389
        foreach ($this->data_peers as $peer) {
390
            $conf .= "exten => {$peer['extension']},1,Goto(internal-users,{$peer['extension']},1) \n";
391
        }
392
        $conf .= "\n";
393
394
        return $conf;
395
    }
396
397
    public function extensionGenInternalTransfer(): string
398
    {
399
        if ($this->data_peers === null) {
400
            $this->getSettings();
401
        }
402
        // Генерация внутреннего номерного плана.
403
        $conf = '';
404
        foreach ($this->data_peers as $peer) {
405
            $conf .= "exten => {$peer['extension']},1,Set(__ISTRANSFER=transfer_) \n";
406
            $conf .= "	same => n,Goto(internal-users,{$peer['extension']},1) \n";
407
        }
408
        $conf .= "\n";
409
410
        return $conf;
411
    }
412
413
    /**
414
     * Генератор sip.conf
415
     *
416
     * @return bool|void
417
     */
418
    protected function generateConfigProtected(): void
419
    {
420
        $conf = '';
421
        $conf .= $this->generateGeneralPj();
422
        $conf .= $this->generateProvidersPj();
423
        $conf .= $this->generatePeersPj();
424
425
        Util::fileWriteContent($this->config->path('asterisk.astetcdir') . '/pjsip.conf', $conf);
426
        $pjConf = '[log_mappings]' . "\n" .
427
            'type=log_mappings' . "\n" .
428
            'asterisk_error = 0' . "\n" .
429
            'asterisk_warning = 2' . "\n" .
430
            'asterisk_debug = 1,3,4,5,6' . "\n\n";
431
432
        file_put_contents($this->config->path('asterisk.astetcdir') . '/pjproject.conf', $pjConf);
433
        file_put_contents($this->config->path('asterisk.astetcdir') . '/sorcery.conf', '');
434
435
        $db = new AstDB();
436
        foreach ($this->data_peers as $peer) {
437
            // Помещаем в AstDB сведения по маршуртизации.
438
            $ringlength = ($peer['ringlength'] === '0') ? '' : trim($peer['ringlength']);
439
            $db->databasePut('FW_TIME', $peer['extension'], $ringlength);
440
            $db->databasePut('FW', $peer['extension'], trim($peer['forwarding']));
441
            $db->databasePut('FW_BUSY', $peer['extension'], trim($peer['forwardingonbusy']));
442
            $db->databasePut('FW_UNAV', $peer['extension'], trim($peer['forwardingonunavailable']));
443
        }
444
    }
445
446
    /**
447
     * Генератора секции general pjsip.conf
448
     *
449
     *
450
     * @return string
451
     */
452
    private function generateGeneralPj(): string
453
    {
454
        $lang = $this->generalSettings['PBXLanguage'];
455
        [$topology, $extipaddr, $exthostname, $subnets] = $this->getTopologyData();
456
457
        $codecs    = $this->getCodecs();
458
        $codecConf = '';
459
        foreach ($codecs as $codec) {
460
            $codecConf .= "allow = {$codec}\n";
461
        }
462
463
        $pbxVersion = PbxSettings::getValueByKey('PBXVersion');
464
        $natConf    = '';
465
        if ($topology === 'private') {
466
            foreach ($subnets as $net) {
467
                $natConf .= "local_net={$net}\n";
468
            }
469
            if ( ! empty($exthostname)) {
470
                $parts   = explode(':', $exthostname);
471
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
472
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
473
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
474
            } elseif ( ! empty($extipaddr)) {
475
                $parts   = explode(':', $extipaddr);
476
                $natConf .= 'external_media_address=' . $parts[0] . "\n";
477
                $natConf .= 'external_signaling_address=' . $parts[0] . "\n";
478
                $natConf .= 'external_signaling_port=' . ($parts[1] ?? '5060');
479
            }
480
        }
481
482
        $conf = "[general] \n" .
483
            "disable_multi_domain=on\n" .
484
            "transport = udp \n\n" .
485
486
            "[global] \n" .
487
            "type = global\n" .
488
            "endpoint_identifier_order=username,ip,anonymous\n" .
489
            "user_agent = mikopbx-{$pbxVersion}\n\n" .
490
491
            "[transport-udp]\n" .
492
            "type = transport\n" .
493
            "protocol = udp\n" .
494
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
495
            "{$natConf}\n\n" .
496
497
            "[transport-tcp]\n" .
498
            "type = transport\n" .
499
            "protocol = tcp\n" .
500
            "bind=0.0.0.0:{$this->generalSettings['SIPPort']}\n" .
501
            "{$natConf}\n\n" .
502
            '';
503
504
        $allowGuestCalls = PbxSettings::getValueByKey('PBXAllowGuestCalls');
505
        if ($allowGuestCalls === '1') {
506
            $conf .= "[anonymous]\n" .
507
                "type = endpoint\n" .
508
                $codecConf .
509
                "language={$lang}\n" .
510
                "timers = no\n" .
511
                "context = public-direct-dial\n\n";
512
        }
513
514
        $varEtcDir = $this->config->path('core.varEtcDir');
515
        file_put_contents(
516
            $varEtcDir . '/topology_hash',
517
            md5($topology . $exthostname . $extipaddr . $this->generalSettings['SIPPort'])
518
        );
519
        $conf .= "\n";
520
521
        return $conf;
522
    }
523
524
    /**
525
     * Генератор секции провайдеров в sip.conf
526
     *
527
     *
528
     * @return string
529
     */
530
    private function generateProvidersPj(): string
531
    {
532
        $conf        = '';
533
        $reg_strings = '';
534
        $prov_config = '';
535
        if ($this->data_providers === null) {
536
            $this->getSettings();
537
        }
538
        foreach ($this->data_providers as $provider) {
539
            $manual_attributes = Util::parseIniSettings(base64_decode($provider['manualattributes'] ?? ''));
540
            $provider['port']  = (trim($provider['port']) === '') ? '5060' : $provider['port'];
541
542
            $reg_strings .= $this->generateProviderRegistrationAuth($provider, $manual_attributes);
543
            $reg_strings .= $this->generateProviderRegistration($provider, $manual_attributes);
544
            $prov_config .= $this->generateProviderOutAuth($provider, $manual_attributes);
545
546
            $prov_config .= $this->generateProviderAor($provider, $manual_attributes);
547
            $prov_config .= $this->generateProviderIdentify($provider, $manual_attributes);
548
            $prov_config .= $this->generateProviderEndpoint($provider, $manual_attributes);
549
        }
550
551
        $conf .= $reg_strings;
552
        $conf .= $prov_config;
553
554
        return $conf;
555
    }
556
557
    /**
558
     * Генерация Auth для секции Registration провайдера.
559
     *
560
     * @param array $provider
561
     * @param array $manual_attributes
562
     *
563
     * @return string
564
     */
565
    private function generateProviderRegistrationAuth(
566
        array $provider,
567
        array $manual_attributes
568
    ): string {
569
        $conf = '';
570
        if ($provider['noregister'] === '1') {
571
            return $conf;
572
        }
573
        $options         = [
574
            'type'     => 'registration-auth',
575
            'username' => $provider['username'],
576
            'password' => $provider['secret'],
577
        ];
578
        $options         = $this->overridePJSIPOptionsFromModules(
579
            $provider['uniqid'],
580
            $options,
581
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
582
        );
583
        $options['type'] = 'auth';
584
        $conf            .= "[REG-AUTH-{$provider['uniqid']}]\n";
585
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration-auth');
586
587
        return $conf;
588
    }
589
590
    /**
591
     * Calls an overridePJSIPOptions function from additional modules
592
     *
593
     * @param $extension
594
     * @param $options
595
     * @param $method
596
     *
597
     * @return array
598
     */
599
    private function overridePJSIPOptionsFromModules($extension, $options, $method): array
600
    {
601
        foreach ($this->additionalModules as $configClassObj) {
602
            if ( ! method_exists($configClassObj, $method)) {
603
                continue;
604
            }
605
            try {
606
                $newOptionsSet = call_user_func_array([$configClassObj, $method], [$extension, $options]);
607
            } catch (Throwable $e) {
608
                global $errorLogger;
609
                $errorLogger->captureException($e);
610
                Util::sysLogMsg(__METHOD__, $e->getMessage(), LOG_ERR);
611
                continue;
612
            }
613
            $options = $newOptionsSet;
614
        }
615
616
        return $options;
617
    }
618
619
    /**
620
     * Генерация Registration провайдера.
621
     *
622
     * @param array $provider
623
     * @param array $manual_attributes
624
     *
625
     * @return string
626
     */
627
    private function generateProviderRegistration(
628
        array $provider,
629
        array $manual_attributes
630
    ): string {
631
        $conf = '';
632
        if ($provider['noregister'] === '1') {
633
            return $conf;
634
        }
635
        $options = [
636
            'type'                     => 'registration',
637
            'outbound_auth'            => "REG-AUTH-{$provider['uniqid']}",
638
            'contact_user'             => $provider['username'],
639
            'retry_interval'           => '30',
640
            'max_retries'              => '100',
641
            'forbidden_retry_interval' => '300',
642
            'fatal_retry_interval'     => '300',
643
            'expiration'               => $this->generalSettings['SIPDefaultExpiry'],
644
            'server_uri'               => "sip:{$provider['host']}:{$provider['port']}",
645
            'client_uri'               => "sip:{$provider['username']}@{$provider['host']}:{$provider['port']}",
646
        ];
647
        $options = $this->overridePJSIPOptionsFromModules(
648
            $provider['uniqid'],
649
            $options,
650
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
651
        );
652
        $conf    .= "[REG-{$provider['uniqid']}] \n";
653
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'registration');
654
655
        return $conf;
656
    }
657
658
    /**
659
     * Генерация Auth провайдера.
660
     *
661
     * @param array $provider
662
     * @param array $manual_attributes
663
     *
664
     * @return string
665
     */
666
    private function generateProviderOutAuth(
667
        array $provider,
668
        array $manual_attributes
669
    ): string {
670
        $conf = '';
671
        if ('1' === $provider['receive_calls_without_auth']) {
672
            return $conf;
673
        }
674
        $options         = [
675
            'type'     => 'endpoint-auth',
676
            'username' => $provider['username'],
677
            'password' => $provider['secret'],
678
        ];
679
        $options         = $this->overridePJSIPOptionsFromModules(
680
            $provider['uniqid'],
681
            $options,
682
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
683
        );
684
        $options['type'] = 'auth';
685
        $conf            .= "[{$provider['uniqid']}-OUT]\n";
686
        $conf            .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint-auth');
687
688
        return $conf;
689
    }
690
691
    /**
692
     * Генерация AOR для Endpoint.
693
     *
694
     * @param array $provider
695
     * @param array $manual_attributes
696
     *
697
     * @return string
698
     */
699
    private function generateProviderAor(array $provider, array $manual_attributes): string
700
    {
701
        $conf        = '';
702
        $defaultuser = (trim($provider['defaultuser']) === '') ? $provider['username'] : $provider['defaultuser'];
703
        if ( ! empty($defaultuser) && '1' !== $provider['receive_calls_without_auth']) {
704
            $contact = "sip:$defaultuser@{$provider['host']}:{$provider['port']}";
705
        } else {
706
            $contact = "sip:{$provider['host']}:{$provider['port']}";
707
        }
708
        $options = [
709
            'type'               => 'aor',
710
            'max_contacts'       => '1',
711
            'contact'            => $contact,
712
            'maximum_expiration' => $this->generalSettings['SIPMaxExpiry'],
713
            'minimum_expiration' => $this->generalSettings['SIPMinExpiry'],
714
            'default_expiration' => $this->generalSettings['SIPDefaultExpiry'],
715
        ];
716
        if ($provider['qualify'] === '1') {
717
            $options['qualify_frequency'] = $provider['qualifyfreq'];
718
            $options['qualify_timeout']   = '3.0';
719
        }
720
        $options = $this->overridePJSIPOptionsFromModules(
721
            $provider['uniqid'],
722
            $options,
723
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
724
        );
725
        $conf    .= "[{$provider['uniqid']}]\n";
726
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
727
728
        return $conf;
729
    }
730
731
    /**
732
     * Генерация AOR для Endpoint.
733
     *
734
     * @param array $provider
735
     * @param array $manual_attributes
736
     *
737
     * @return string
738
     */
739
    private function generateProviderIdentify(
740
        array $provider,
741
        array $manual_attributes
742
    ): string {
743
        $conf          = '';
744
        $providerHosts = $this->dataSipHosts[$provider['uniqid']] ?? [];
745
        if ( ! in_array($provider['host'], $providerHosts, true)) {
746
            $providerHosts[] = $provider['host'];
747
        }
748
        $options = [
749
            'type'     => 'identify',
750
            'endpoint' => $provider['uniqid'],
751
            'match'    => implode(',', array_unique($providerHosts)),
752
        ];
753
        $options = $this->overridePJSIPOptionsFromModules(
754
            $provider['uniqid'],
755
            $options,
756
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
757
        );
758
        $conf    .= "[{$provider['uniqid']}]\n";
759
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'identify');
760
761
        return $conf;
762
    }
763
764
    /**
765
     * Генерация Endpoint провайдера.
766
     *
767
     * @param array $provider
768
     * @param array $manual_attributes
769
     *
770
     * @return string
771
     */
772
    private function generateProviderEndpoint(
773
        array $provider,
774
        array $manual_attributes
775
    ): string {
776
        $conf       = '';
777
        $fromdomain = (trim($provider['fromdomain']) === '') ? $provider['host'] : $provider['fromdomain'];
778
        $from       = (trim(
779
                $provider['fromuser']
780
            ) === '') ? "{$provider['username']}; username" : "{$provider['fromuser']}; fromuser";
781
        $from_user  = ($provider['disablefromuser'] === '1') ? null : $from;
782
        $language   = $this->generalSettings['PBXLanguage'];
783
784
        if (count($this->contexts_data[$provider['context_id']]) === 1) {
785
            $context_id = $provider['uniqid'];
786
        } else {
787
            $context_id = $provider['context_id'];
788
        }
789
        $dtmfmode = ($provider['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $provider['dtmfmode'];
790
        $options  = [
791
            'type'            => 'endpoint',
792
            '100rel'          => "no",
793
            'context'         => "{$context_id}-incoming",
794
            'dtmf_mode'       => $dtmfmode,
795
            'disallow'        => 'all',
796
            'allow'           => $provider['codecs'],
797
            'rtp_symmetric'   => 'yes',
798
            'force_rport'     => 'yes',
799
            'rewrite_contact' => 'yes',
800
            'ice_support'     => 'no',
801
            'direct_media'    => 'no',
802
            'from_user'       => $from_user,
803
            'from_domain'     => $fromdomain,
804
            'sdp_session'     => 'mikopbx',
805
            'language'        => $language,
806
            'aors'            => $provider['uniqid'],
807
            'timers'          => ' no',
808
        ];
809
        if ('1' !== $provider['receive_calls_without_auth']) {
810
            $options['outbound_auth'] = "{$provider['uniqid']}-OUT";
811
        }
812
        self::getToneZone($options, $language);
813
        $options = $this->overridePJSIPOptionsFromModules(
814
            $provider['uniqid'],
815
            $options,
816
            CoreConfigClass::OVERRIDE_PROVIDER_PJSIP_OPTIONS
817
        );
818
        $conf    .= "[{$provider['uniqid']}]\n";
819
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
820
821
        return $conf;
822
    }
823
824
    /**
825
     * @param array  $options
826
     * @param string $lang
827
     */
828
    public static function getToneZone(array &$options, string $lang): void
829
    {
830
        $settings = [
831
            'ru-ru' => 'ru',
832
            'en-gb' => 'uk',
833
            'de-de' => 'de',
834
            'da-dk' => 'dk',
835
            'es-es' => 'es',
836
            'fr-ca' => 'fr',
837
            'it-it' => 'it',
838
            'ja-jp' => 'jp',
839
            'nl-nl' => 'nl',
840
            'pl-pl' => 'pl',
841
            'pt-br' => 'pt',
842
        ];
843
        $toneZone = $settings[$lang] ?? '';
844
        if ( ! empty($toneZone)) {
845
            $options['inband_progress'] = 'yes';
846
            $options['tone_zone']       = $toneZone;
847
        }
848
    }
849
850
    /**
851
     * Генератор сеции пиров для sip.conf
852
     *
853
     *
854
     * @return string
855
     */
856
    public function generatePeersPj(): string
857
    {
858
        if ($this->data_peers === null) {
859
            $this->getSettings();
860
        }
861
        $lang = $this->generalSettings['PBXLanguage'];
862
        $conf = '';
863
864
        foreach ($this->data_peers as $peer) {
865
            $manual_attributes = Util::parseIniSettings($peer['manualattributes'] ?? '');
866
            $conf              .= $this->generatePeerAuth($peer, $manual_attributes);
867
            $conf              .= $this->generatePeerAor($peer, $manual_attributes);
868
            $conf              .= $this->generatePeerEndpoint($lang, $peer, $manual_attributes);
869
        }
870
871
        $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEERS_PJ);
872
873
        return $conf;
874
    }
875
876
    /**
877
     * Генерация AOR для Endpoint.
878
     *
879
     * @param array $peer
880
     * @param array $manual_attributes
881
     *
882
     * @return string
883
     */
884
    private function generatePeerAuth(array $peer, array $manual_attributes): string
885
    {
886
        $conf    = '';
887
        $options = [
888
            'type'     => 'auth',
889
            'username' => $peer['extension'],
890
            'password' => $peer['secret'],
891
        ];
892
        $options = $this->overridePJSIPOptionsFromModules(
893
            $peer['extension'],
894
            $options,
895
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
896
        );
897
        $conf    .= "[{$peer['extension']}] \n";
898
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'auth');
899
900
        return $conf;
901
    }
902
903
    /**
904
     * Генерация AOR для Endpoint.
905
     *
906
     * @param array $peer
907
     * @param array $manual_attributes
908
     *
909
     * @return string
910
     */
911
    private function generatePeerAor(array $peer, array $manual_attributes): string
912
    {
913
        $conf    = '';
914
        $options = [
915
            'type'              => 'aor',
916
            'qualify_frequency' => '60',
917
            'qualify_timeout'   => '5',
918
            'max_contacts'      => '5',
919
        ];
920
        $options = $this->overridePJSIPOptionsFromModules(
921
            $peer['extension'],
922
            $options,
923
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
924
        );
925
        $conf    .= "[{$peer['extension']}] \n";
926
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'aor');
927
928
        return $conf;
929
    }
930
931
    /**
932
     * Генерация endpoint.
933
     *
934
     * @param        $lang
935
     * @param array  $peer
936
     * @param array  $manual_attributes
937
     *
938
     * @return string
939
     */
940
    private function generatePeerEndpoint(
941
        $lang,
942
        array $peer,
943
        array $manual_attributes
944
    ): string {
945
        $conf     = '';
946
        $language = str_replace('_', '-', strtolower($lang));
947
        $language = (trim($language) === '') ? 'en-en' : $language;
948
949
        $calleridname = (trim($peer['calleridname']) === '') ? $peer['extension'] : $peer['calleridname'];
950
        $busylevel    = (trim($peer['busylevel']) === '') ? '1' : '' . $peer['busylevel'];
951
952
        $dtmfmode = ($peer['dtmfmode'] === 'rfc2833') ? 'rfc4733' : $peer['dtmfmode'];
953
        $options  = [
954
            'type'                 => 'endpoint',
955
            'context'              => 'all_peers',
956
            'dtmf_mode'            => $dtmfmode,
957
            'disallow'             => 'all',
958
            'allow'                => $peer['codecs'],
959
            'rtp_symmetric'        => 'yes',
960
            'force_rport'          => 'yes',
961
            'rewrite_contact'      => 'yes',
962
            'ice_support'          => 'no',
963
            'direct_media'         => 'no',
964
            'callerid'             => "{$calleridname} <{$peer['extension']}>",
965
            'send_pai'             => 'yes',
966
            'call_group'           => '1',
967
            'pickup_group'         => '1',
968
            'sdp_session'          => 'mikopbx',
969
            'language'             => $language,
970
            'mailboxes'            => 'admin@voicemailcontext',
971
            'device_state_busy_at' => $busylevel,
972
            'aors'                 => $peer['extension'],
973
            'auth'                 => $peer['extension'],
974
            'outbound_auth'        => $peer['extension'],
975
            'acl'                  => "acl_{$peer['extension']}",
976
            'timers'               => ' no',
977
        ];
978
        self::getToneZone($options, $language);
979
        $options = $this->overridePJSIPOptionsFromModules(
980
            $peer['extension'],
981
            $options,
982
            CoreConfigClass::OVERRIDE_PJSIP_OPTIONS
983
        );
984
        $conf    .= "[{$peer['extension']}] \n";
985
        $conf    .= Util::overrideConfigurationArray($options, $manual_attributes, 'endpoint');
986
        $conf    .= $this->hookModulesMethod(CoreConfigClass::GENERATE_PEER_PJ_ADDITIONAL_OPTIONS, [$peer]);
987
988
        return $conf;
989
    }
990
991
}