Passed
Push — develop ( f628fc...4f13da )
by Портнов
04:39
created

ExtensionsConf::generateIncomingContextPeers()   F

Complexity

Conditions 45
Paths > 20000

Size

Total Lines 186
Code Lines 121

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 121
c 4
b 0
f 0
dl 0
loc 186
rs 0
cc 45
nc 1152145
nop 3

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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\{Iax, IncomingRoutingTable, OutgoingRoutingTable, OutWorkTimes, Providers, Sip, SoundFiles};
23
use MikoPBX\Common\Providers\PBXConfModulesProvider;
24
use MikoPBX\Core\Asterisk\Configs\Generators\Extensions\IncomingContexts;
25
use MikoPBX\Modules\Config\ConfigClass;
26
use MikoPBX\Core\System\{MikoPBXConfig, Storage, Util};
27
use Phalcon\Di;
28
29
class ExtensionsConf extends ConfigClass
30
{
31
    protected string $description = 'extensions.conf';
32
33
    /**
34
     * Sorts array by priority field
35
     *
36
     * @param $a
37
     * @param $b
38
     *
39
     * @return int|null
40
     */
41
    public static function sortArrayByPriority(array $a, array $b): int
42
    {
43
        $aPriority = (int)($a['priority'] ?? 0);
44
        $bPriority = (int)($b['priority'] ?? 0);
45
        if ($aPriority === $bPriority) {
46
            return 0;
47
        }
48
49
        return ($aPriority < $bPriority) ? -1 : 1;
50
    }
51
52
    /**
53
     * Основной генератор extensions.conf
54
     */
55
    protected function generateConfigProtected(): void
56
    {
57
        /** @scrutinizer ignore-call */
58
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
59
        $conf              = "[globals] \n" .
60
            "TRANSFER_CONTEXT=internal-transfer; \n";
61
        if ($this->generalSettings['PBXRecordCalls'] === '1') {
62
            $conf .= "MONITOR_DIR=" . Storage::getMonitorDir() . " \n";
63
            $conf .= "MONITOR_STEREO=" . $this->generalSettings['PBXSplitAudioThread'] . " \n";
64
        }
65
        foreach ($additionalModules as $appClass) {
66
            $addition = $appClass->extensionGlobals();
67
            if ( ! empty($addition)) {
68
                $conf .= $appClass->confBlockWithComments($addition);
69
            }
70
        }
71
        $conf .= "\n";
72
        $conf .= "\n";
73
        $conf .= "[general] \n";
74
        $conf .= "\n";
75
76
        // Создаем диалплан внутренних учеток.
77
        $this->generateOtherExten($conf);
78
        // Контекст для внутренних вызовов.
79
        $this->generateInternal($conf);
80
        // Контекст для внутренних переадресаций.
81
        $this->generateInternalTransfer($conf);
82
        // Создаем контекст хинтов.
83
        $this->generateSipHints($conf);
84
        // Создаем контекст (исходящие звонки).
85
        $this->generateOutContextPeers($conf);
86
        // Описываем контекст для публичных входящих.
87
        $this->generatePublicContext($conf);
88
89
        Util::fileWriteContent($this->config->path('asterisk.astetcdir') . '/extensions.conf', $conf);
90
    }
91
92
    /**
93
     * Генератор прочих контекстов.
94
     *
95
     * @param $conf
96
     */
97
    private function generateOtherExten(&$conf): void
98
    {
99
        $extension = 'X!';
100
        // Контекст для AMI originate. Без него отображается не корректный CallerID.
101
        $conf .= '[sipregistrations]' . "\n\n";
102
103
        $conf .= '[messages]' . "\n" .
104
            'exten => _' . $extension . ',1,MessageSend(sip:${EXTEN},"${CALLERID(name)}"${MESSAGE(from)})' . "\n\n";
105
106
        $conf .= '[internal-originate]' . " \n";
107
        $conf .= 'exten => _' . $extension . ',1,NoOP(Hint ${HINT} exten ${EXTEN} )' . " \n";
108
        $conf .= '; Если это originate, то скроем один CDR.' . " \n\t";
109
        $conf .= 'same => n,ExecIf($["${pt1c_cid}x" != "x"]?Set(CALLERID(num)=${pt1c_cid}))' . " \n\t";
110
111
        $conf .= 'same => n,ExecIf($["${CUT(CHANNEL,\;,2)}" == "2"]?Set(__PT1C_SIP_HEADER=${SIPADDHEADER}))' . " \n\t";
112
        $conf .= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Set(ADDITIONAL_PEER=&Local/${peer_mobile}@outgoing/n))' . " \n\t";
113
114
        // Описываем возможность прыжка в пользовательский sub контекст.
115
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
116
        $conf .= 'same => n,Dial(Local/${EXTEN}@internal-users/n${ADDITIONAL_PEER},60,TteKkHhb(originate_create_chan,s,1))' . " \n\n";
117
118
        $conf .= '[originate_create_chan]' . " \n";
119
        $conf .= 'exten => s,1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
120
        $conf .= 'same => n,return' . " \n\n";
121
122
        $conf .= '[dial_create_chan]' . " \n";
123
        $conf .= 'exten => s,1,Gosub(lua_${ISTRANSFER}dial_create_chan,${EXTEN},1)' . "\n\t";
124
        $conf .= 'same => n,Set(pt1c_is_dst=1)' . " \n\t";
125
        $conf .= 'same => n,ExecIf($["${PT1C_SIP_HEADER}x" != "x"]?Set(PJSIP_HEADER(add,${CUT(PT1C_SIP_HEADER,:,1)})=${CUT(PT1C_SIP_HEADER,:,2)}))' . " \n\t";
126
        $conf .= 'same => n,Set(__PT1C_SIP_HEADER=${UNDEFINED})' . " \n\t";
127
        $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t";
128
        $conf .= 'same => n,return' . " \n\n";
129
130
        $conf .= '[hangup_handler]' . "\n";
131
        $conf .= 'exten => s,1,NoOp(--- hangup - ${CHANNEL} ---)' . "\n\t";
132
        $conf .= 'same => n,Gosub(hangup_chan,${EXTEN},1)' . "\n\t";
133
134
        $conf .= 'same => n,return' . "\n\n";
135
136
        $conf .= '[set_orign_chan]' . "\n";
137
        $conf .= 'exten => s,1,Wait(0.2)' . "\n\t";
138
        $conf .= 'same => n,Set(pl=${IF($["${CHANNEL:-1}" == "1"]?2:1)})' . "\n\t";
139
        $conf .= 'same => n,Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)})' . "\n\t";
140
        $conf .= 'same => n,ExecIf($[ "${orign_chan}x" == "x" ]?Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},FROM_CHAN)}))' . "\n\t";
141
        $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN}x" != "x" ]?Set(__QUEUE_SRC_CHAN=${orign_chan}))' . "\n\t";
142
        $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN:0:5}" == "Local" ]?Set(__QUEUE_SRC_CHAN=${FROM_CHAN}))' . "\n\t";
143
        $conf .= 'same => n,ExecIf($[ "${FROM_CHAN}x" == "x" ]?Set(__FROM_CHAN=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)}))' . "\n\t";
144
        $conf .= 'same => n,return' . "\n\n";
145
146
        $conf .= '[playback]' . "\n";
147
        $conf .= 'exten => s,1,Playback(hello_demo,noanswer)' . "\n\t";
148
        $conf .= 'same => n,ExecIf($["${SRC_BRIDGE_CHAN}x" == "x"]?Wait(30))' . "\n\t";
149
        $conf .= 'same => n,Wait(0.3)' . "\n\t";
150
        $conf .= 'same => n,Bridge(${SRC_BRIDGE_CHAN},kKTthH)' . "\n\n";
151
152
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
153
154
        // TODO / Добавление / удаление префиксов на входящий callerid.
155
        $conf .= '[add-trim-prefix-clid]' . "\n";
156
        $conf .= 'exten => _.!,1,NoOp(--- Incoming call from ${CALLERID(num)} ---)' . "\n\t";
157
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
158
        // Отсекаем "+".
159
        // $conf.= 'same => n,ExecIf( $["${CALLERID(num):0:1}" == "+"]?Set(CALLERID(num)=${CALLERID(num):1}))'."\n\t";
160
        // Отсекаем "7" и добавляем "8".
161
        // $conf.= 'same => n,ExecIf( $["${REGEX("^7[0-9]+" ${CALLERID(num)})}" == "1"]?Set(CALLERID(num)=8${CALLERID(num):1}))'."\n\t";
162
        $conf .= 'same => n,return' . "\n\n";
163
    }
164
165
    /**
166
     * Генератор контекста для внутренних вызовов.
167
     *
168
     * @param $conf
169
     */
170
    private function generateInternal(&$conf): void
171
    {
172
        $extension  = 'X!';
173
        $technology = SIPConf::getTechnology();
174
175
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
176
        foreach ($additionalModules as $appClass) {
177
            $addition = $appClass->extensionGenContexts();
178
            if ( ! empty($addition)) {
179
                $conf .= $appClass->confBlockWithComments($addition);
180
            }
181
        }
182
        $conf .= "\n";
183
        $conf .= "[internal-num-undefined] \n";
184
        $conf .= 'exten => _' . $extension . ',1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
185
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t";
186
        $conf .= "same => n,Playback(pbx-invalid,noanswer) \n\n";
187
188
        $conf .= "[internal-fw]\n";
189
        $conf .= 'exten => _' . $extension . ',1,NoOp(DIALSTATUS - ${DIALSTATUS})' . "\n\t";
190
        // CANCEL - вызов был отменен, к примеру *0, не нужно дальше искать адресат.
191
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CANCEL"]?Hangup())' . "\n\t";
192
        // BUSY - занято. К примру абонент завершил вызов или DND.
193
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "BUSY"]?Set(dstatus=FW_BUSY))' . "\n\t";
194
        // CHANUNAVAIL - канал не доступен. К примеру телефон не зарегистрирован или не отвечает.
195
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CHANUNAVAIL"]?Set(dstatus=FW_UNAV))' . "\n\t";
196
        // NOANSWER - не ответили по таймауту.
197
        $conf .= 'same => n,ExecIf($["${dstatus}x" == "x"]?Set(dstatus=FW))' . "\n\t";
198
        $conf .= 'same => n,Set(fw=${DB(${dstatus}/${EXTEN})})' . "\n\t";
199
        $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Set(__pt1c_UNIQUEID=${UNDEFINED})' . "\n\t";
200
        $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Goto(internal,${fw},1))' . "\n\t";
201
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t";
202
        $conf .= 'same => n,Hangup() ' . "\n\n";
203
204
        $conf .= "[all_peers]\n";
205
        $conf .= 'include => internal-hints' . "\n";
206
        $conf .= 'exten => failed,1,Hangup()' . "\n";
207
208
        $conf .= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())' . "\n\t";
209
        // Фильтр спецсимволов. Разершаем только цифры.
210
        $conf .= 'same => n,Set(cleanNumber=${FILTER(\*\#\+1234567890,${EXTEN})})' . "\n\t";
211
        $conf .= 'same => n,ExecIf($["${EXTEN}" != "${cleanNumber}"]?Goto(${CONTEXT},${cleanNumber},$[${PRIORITY} + 1]))' . "\n\t";
212
213
        $conf .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
214
        $conf .= 'same => n,ExecIf($["${OLD_LINKEDID}x" == "x"]?Set(__OLD_LINKEDID=${CHANNEL(linkedid)}))' . "\n\t";
215
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
216
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . "\n\t";
217
218
        $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(num)=${FROM_PEER}))' . "\n\t";
219
        $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(name)=${FROM_PEER}))' . "\n\t";
220
221
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local" && "${FROM_PEER}x" == "x"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
222
        $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
223
        $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
224
225
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
226
        $dialplanNames = ['applications', 'internal', 'outgoing'];
227
        foreach ($dialplanNames as $name){
228
            $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS('.$name.',${EXTEN},1)}" == "1"]?'.$name.',${EXTEN},1)'." \n\t";
229
        }
230
        $conf .= 'same => n,Hangup()'." \n";
231
232
        $pickupexten  = $this->generalSettings['PBXFeaturePickupExten'];
233
        $conf        .= 'exten => _' . $pickupexten . $extension . ',1,Set(PICKUPEER=' . $technology . '/${FILTER(0-9,${EXTEN:2})})' . "\n\t";
234
        $conf        .= 'same => n,Set(pt1c_dnid=${EXTEN})' . "\n\t";
235
        $conf        .= 'same => n,PickupChan(${PICKUPEER})' . "\n\t";
236
        $conf        .= 'same => n,Hangup()' . "\n\n";
237
238
        $voicemail_exten  = $this->generalSettings['VoicemailExten'];
239
        $conf            .= 'exten => ' . $voicemail_exten . ',1,NoOp(NOTICE, Dialing out from ${CALLERID(all)} to VoiceMail)' . "\n\t";
240
        $conf            .= 'same => n,VoiceMailMain(admin@voicemailcontext,s)' . "\n\t";
241
        $conf            .= 'same => n,Hangup()' . "\n\n";
242
243
        $conf .= "[voice_mail_peer] \n";
244
        $conf .= 'exten => voicemail,1,Answer()' . "\n\t";
245
        $conf .= 'same => n,VoiceMail(admin@voicemailcontext)' . "\n\t";
246
        $conf .= 'same => n,Hangup()' . "\n\n";
247
248
        // Контекст для внутренних вызовов.
249
        $conf .= "[internal] \n";
250
251
        foreach ($additionalModules as $appClass) {
252
            $addition = $appClass->getIncludeInternal();
253
            if ( ! empty($addition)) {
254
                $conf .= $appClass->confBlockWithComments($addition);
255
            }
256
        }
257
258
        foreach ($additionalModules as $appClass) {
259
            $addition = $appClass->extensionGenInternal();
260
            if ( ! empty($addition)) {
261
                $conf .= $appClass->confBlockWithComments($addition);
262
            }
263
        }
264
265
        $conf .= 'exten => i,1,NoOp(-- INVALID NUMBER --)' . "\n\t";
266
        $conf .= 'same => n,Set(DIALSTATUS=INVALID_NUMBER)' . "\n\t";
267
        $conf .= 'same => n,Playback(privacy-incorrect,noanswer)' . "\n\t";
268
        $conf .= 'same => n,Hangup()' . "\n";
269
270
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
271
272
        $conf .= "[internal-incoming]\n";
273
        $conf .= 'exten => _.!,1,ExecIf($["${MASTER_CHANNEL(M_TIMEOUT)}x" != "x"]?Set(TIMEOUT(absolute)=${MASTER_CHANNEL(M_TIMEOUT)}))' . " \n\t";
274
        $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT_CHANNEL)=${CHANNEL})' . " \n\t";
275
        $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT)=${EMPTY_VAR})' . " \n\t";
276
        $conf .= 'same => n,Goto(internal,${EXTEN},1)' . " \n\n";
277
278
        $conf .= "[internal-users] \n";
279
        $conf .= 'exten => _' . $extension . ',1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t";
280
        $conf .= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Set(SIPADDHEADER01=${EMPTY_VAR})' . " \n\t";
281
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . " \n\t";
282
283
        $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
284
        // Проверим, существует ли такой пир.
285
286
        $conf .= 'same => n,ExecIf($["${PJSIP_ENDPOINT(${EXTEN},auth)}x" == "x"]?Goto(internal-num-undefined,${EXTEN},1))' . " \n\t";
287
        $conf .= 'same => n,ExecIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?Set(DIALSTATUS=BUSY))' . " \n\t";
288
        $conf .= 'same => n,GotoIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?fw_start)' . " \n\t";
289
290
        // Как долго звонить пиру.
291
        $conf .= 'same => n,Set(ringlength=${DB(FW_TIME/${EXTEN})})' . " \n\t";
292
        $conf .= 'same => n,ExecIf($["${ringlength}x" == "x"]?Set(ringlength=600))' . " \n\t";
293
        $conf .= 'same => n,ExecIf($["${QUEUE_SRC_CHAN}x" != "x" && "${ISTRANSFER}x" == "x"]?Set(ringlength=600))' . " \n\t";
294
295
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1) ' . " \n\t";
296
        // Совершаем вызов пира.
297
        $conf .= 'same => n,Set(DST_CONTACT=${PJSIP_DIAL_CONTACTS(${EXTEN})})' . " \n\t";
298
        $conf .= 'same => n,ExecIf($["${FIELDQTY(DST_CONTACT,&)}" != "1"]?Set(__PT1C_SIP_HEADER=${EMPTY_VAR}))' . " \n\t";
299
        $conf .= 'same => n,ExecIf($["${DST_CONTACT}x" != "x"]?Dial(${DST_CONTACT},${ringlength},TtekKHhU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1)):Set(DIALSTATUS=CHANUNAVAIL))' . " \n\t";
300
        $conf .= 'same => n(fw_start),NoOp(dial_hangup)' . " \n\t";
301
302
        // QUEUE_SRC_CHAN - установлена, если вызов сервершен агенту очереди.
303
        // Проверяем нужна ли переадресация
304
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${ISTRANSFER}x" != "x"]?Goto(internal-fw,${EXTEN},1))' . " \n\t";
305
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${QUEUE_SRC_CHAN}x" == "x"]?Goto(internal-fw,${EXTEN},1))' . " \n\t";
306
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . " \n\t";
307
        $conf .= 'same => n,Hangup()' . "\n\n";
308
309
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
310
    }
311
312
    /**
313
     * Генератор контекста для переадресаций.
314
     *
315
     * @param $conf
316
     */
317
    private function generateInternalTransfer(&$conf): void
318
    {
319
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
320
        $conf              .= "[internal-transfer] \n";
321
322
        foreach ($additionalModules as $appClass) {
323
            $addition = $appClass->getIncludeInternalTransfer();
324
            if ( ! empty($addition)) {
325
                $conf .= $appClass->confBlockWithComments($addition);
326
            }
327
        }
328
329
        foreach ($additionalModules as $appClass) {
330
            $addition = $appClass->extensionGenInternalTransfer();
331
            if ( ! empty($addition)) {
332
                $conf .= $appClass->confBlockWithComments($addition);
333
            }
334
        }
335
        $conf .= 'exten => h,1,Gosub(transfer_dial_hangup,${EXTEN},1)' . "\n\n";
336
    }
337
338
    /**
339
     * Генератор хинтов SIP.
340
     *
341
     * @param $conf
342
     */
343
    private function generateSipHints(&$conf): void
344
    {
345
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
346
        $conf              .= "[internal-hints] \n";
347
        foreach ($additionalModules as $appClass) {
348
            $addition = $appClass->extensionGenHints();
349
            if ( ! empty($addition)) {
350
                $conf .= $appClass->confBlockWithComments($addition);
351
            }
352
        }
353
        $conf .= "\n\n";
354
    }
355
356
    /**
357
     * Генератор исходящих контекстов.
358
     *
359
     * @param $conf
360
     */
361
    private function generateOutContextPeers(&$conf): void
362
    {
363
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
364
        $conf              .= "[outgoing] \n";
365
366
        $conf .= 'exten => _+.!,1,NoOp(Strip + sign from number and convert it to +)' . " \n\t";
367
        $conf .= 'same => n,Set(ADDPLUS=+);' . " \n\t";
368
        $conf .= 'same => n,Goto(${CONTEXT},${EXTEN:1},1);' . " \n\n";
369
        $conf .= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())' . " \n\t";
370
        $conf .= 'same => n,Ringing()' . " \n\t";
371
372
        // Описываем возможность прыжка в пользовательский sub контекст.
373
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
374
375
        /** @var OutgoingRoutingTable $routs */
376
        /** @var OutgoingRoutingTable $rout */
377
        $routs = OutgoingRoutingTable::find(['order' => 'priority'])->toArray();
378
        uasort($routs, __CLASS__ . '::sortArrayByPriority');
379
380
        $provider_contexts = [];
381
382
        foreach ($routs as $rout) {
383
            $technology = $this->getTechByID($rout['providerid']);
384
            if ($technology !== '') {
385
                $rout_data                       = $rout;
386
                $rout_data['technology']         = $technology;
387
                $id_dialplan                     = $rout_data['providerid'] . '-' . $rout_data['id'] . '-outgoing';
388
                $provider_contexts[$id_dialplan] = $rout_data;
389
                $conf                            .= $this->generateOutgoingRegexPattern($rout_data);
390
                continue;
391
            }
392
        }
393
        $conf .= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Hangup())' . " \n\t";
394
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${BLINDTRANSFER}x" != "x" && "${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
395
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . " \n\t";
396
        $conf .= 'same => n,ExecIf($["${ROUTFOUND}x" == "x"]?Gosub(dial,${EXTEN},1))' . "\n\t";
397
398
        $conf .= 'same => n,Playback(silence/2,noanswer)' . " \n\t";
399
        $conf .= 'same => n,ExecIf($["${ROUTFOUND}x" != "x"]?Playback(followme/sorry,noanswer):Playback(cannot-complete-as-dialed,noanswer))' . " \n\t";
400
        $conf .= 'same => n,Hangup()' . " \n\n";
401
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
402
403
        foreach ($provider_contexts as $id_dialplan => $rout) {
404
            $conf .= "\n[{$id_dialplan}]\n";
405
            $trimFromBegin = (int) ($rout['trimfrombegin']??0);
406
            if ($trimFromBegin > 0) {
407
                $exten_var    = '${ADDPLUS}${EXTEN:' . $rout['trimfrombegin'] . '}';
408
                $change_exten = 'same => n,ExecIf($["${EXTEN}" != "${number}"]?Goto(${CONTEXT},${number},$[${PRIORITY} + 1]))' . "\n\t";
409
            } else {
410
                $exten_var    = '${ADDPLUS}${EXTEN}';
411
                $change_exten = '';
412
            }
413
            $conf .= 'exten => _.!,1,Set(number=' . $rout['prepend'] . $exten_var . ')' . "\n\t";
414
            $conf .= 'same => n,Set(number=${FILTER(\*\#\+1234567890,${number})})' . "\n\t";
415
            $conf .= $change_exten;
416
            foreach ($additionalModules as $appClass) {
417
                $addition = $appClass->generateOutRoutContext($rout);
418
                if ( ! empty($addition)) {
419
                    $conf .= $appClass->confBlockWithComments($addition);
420
                }
421
            }
422
            $conf .= 'same => n,ExecIf($["${number}x" == "x"]?Hangup())' . "\n\t";
423
            $conf .= 'same => n,Set(ROUTFOUND=1)' . "\n\t";
424
            $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
425
426
            $conf .= 'same => n,ExecIf($["${EXTERNALPHONE}" == "${EXTEN}"]?Set(DOPTIONS=tk))' . "\n\t";
427
428
            // Описываем возможность прыжка в пользовательский sub контекст.
429
            $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(' . $rout['providerid'] . '-outgoing-custom,${EXTEN},1)}" == "1"]?' . $rout['providerid'] . '-outgoing-custom,${EXTEN},1)' . "\n\t";
430
431
            if ($rout['technology'] === IAXConf::TYPE_IAX2) {
432
                $conf .= 'same => n,Dial(' . $rout['technology'] . '/' . $rout['providerid'] . '/${number},600,${DOPTIONS}TKU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1))' . "\n\t";
433
            } else {
434
                $conf .= 'same => n,Dial(' . $rout['technology'] . '/${number}@' . $rout['providerid'] . ',600,${DOPTIONS}TKU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1))' . "\n\t";
435
            }
436
            foreach ($additionalModules as $appClass) {
437
                $addition = $appClass->generateOutRoutAfterDialContext($rout);
438
                if ( ! empty($addition)) {
439
                    $conf .= $appClass->confBlockWithComments($addition);
440
                }
441
            }
442
            $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(' . $rout['providerid'] . '-outgoing-after-dial-custom,${EXTEN}),1}" == "1"]?' . $rout['providerid'] . '-outgoing-after-dial-custom,${EXTEN},1)' . "\n\t";
443
444
            $conf .= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
445
            $conf .= 'same => n,ExecIf($["${DIALSTATUS}" = "ANSWER"]?Hangup())' . "\n\t";
446
            $conf .= 'same => n,Set(pt1c_UNIQUEID=${EMPTY_VALUE})' . "\n\t";
447
            $conf .= 'same => n,return' . "\n";
448
        }
449
    }
450
451
    /**
452
     * Генератор extension для контекста outgoing.
453
     *
454
     * @param string $uniqueID
455
     *
456
     * @return null|string
457
     */
458
    public function getTechByID(string $uniqueID): string
459
    {
460
        $technology = '';
461
        $provider   = Providers::findFirstByUniqid($uniqueID);
462
        if ($provider !== null) {
463
            if ($provider->type === 'SIP') {
464
                $account    = Sip::findFirst('disabled="0" AND uniqid = "' . $uniqueID . '"');
465
                $technology = ($account === null) ? '' : SIPConf::getTechnology();
466
            } elseif ($provider->type === 'IAX') {
467
                $account    = Iax::findFirst('disabled="0" AND uniqid = "' . $uniqueID . '"');
468
                $technology = ($account === null) ? '' : 'IAX2';
469
            }
470
        }
471
472
        return $technology;
473
    }
474
475
    /**
476
     * Генератор исходящего маршрута.
477
     *
478
     * @param $rout
479
     *
480
     * @return string
481
     */
482
    private function generateOutgoingRegexPattern($rout): string
483
    {
484
        $conf         = '';
485
        $regexPattern = '';
486
        
487
        $restNumbers = (int) ($rout['restnumbers']??0);
488
        if ($restNumbers > 0) {
489
            $regexPattern = "[0-9]{" . $rout['restnumbers'] . "}$";
490
        } elseif ($restNumbers === 0) {
491
            $regexPattern = "$";
492
        } elseif ($restNumbers === -1) {
493
            $regexPattern = "";
494
        }
495
        $numberBeginsWith = $rout['numberbeginswith']??'';
496
        $numberBeginsWith = str_replace(array('*', '+'), array('\\\\*', '\\\\+'), $numberBeginsWith);
497
        $conf            .= 'same => n,ExecIf($["${REGEX("^' . $numberBeginsWith . $regexPattern . '" ${EXTEN})}" == "1"]?Gosub(' . $rout['providerid'] . '-' . $rout['id'] . '-outgoing,${EXTEN},1))' . " \n\t";
498
499
        return $conf;
500
    }
501
502
    /**
503
     * Контекст для входящих внешних звонков без авторизации.
504
     *
505
     * @param $conf
506
     */
507
    public function generatePublicContext(&$conf): void
508
    {
509
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
510
        $conf              .= "\n";
511
        $conf              .= IncomingContexts::generate('none');
512
        $conf              .= "[public-direct-dial] \n";
513
        foreach ($additionalModules as $appClass) {
514
            if ($appClass instanceof $this) {
515
                continue;
516
            }
517
            $appClass->generatePublicContext($conf);
518
        }
519
        $filter = ["provider IS NULL AND priority<>9999"];
520
521
        /**
522
         * @var array
523
         */
524
        $m_data = IncomingRoutingTable::find($filter);
525
        if (count($m_data->toArray()) > 0) {
526
            $conf .= 'include => none-incoming';
527
        }
528
    }
529
530
}