Passed
Push — master ( 90372d...e80252 )
by Nikolay
25:24
created

Extensions   F

Complexity

Total Complexity 92

Size/Duplication

Total Lines 698
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 92
eloc 413
dl 0
loc 698
c 0
b 0
f 0
rs 2

11 Methods

Rating   Name   Duplication   Size   Complexity  
B generateOutContextPeers() 0 80 10
A generatePublicContext() 0 15 3
F generateIncomingContextPeers() 0 166 38
A generateOtherExten() 0 74 2
A generateSipHints() 0 6 2
A __construct() 0 5 1
A generateOutgoingRegexPattern() 0 13 5
F generateOutWorkTimes() 0 87 20
A generate() 0 27 2
A generateInternalTransfer() 0 12 3
B generateInternal() 0 134 6

How to fix   Complexity   

Complex Class

Complex classes like Extensions often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Extensions, and based on these observations, apply Extract Interface, too.

1
<?php
2
/**
3
 * Copyright © MIKO LLC - All Rights Reserved
4
 * Unauthorized copying of this file, via any medium is strictly prohibited
5
 * Proprietary and confidential
6
 * Written by Alexey Portnov, 2 2020
7
 */
8
9
require_once("globals.php");
10
11
class Extensions{
12
    /**
13
     * @var array
14
     */
15
    private $arrObject;
16
    private $extensionLength;
17
18
    /**
19
     * Extensions constructor.
20
     * @param $arrObject - массив классов генераторов.
0 ignored issues
show
Documentation Bug introduced by
The doc comment - at position 0 could not be parsed: Unknown type name '-' at position 0 in -.
Loading history...
21
     */
22
    function __construct(&$arrObject) {
23
        // Получение настроек.
24
        $this->arrObject = $arrObject;
25
        $config = new Config();
26
        $this->extensionLength = $config->get_general_settings('PBXInternalExtensionLength');
27
    }
28
29
    /**
30
     * Основной генератор exgtensions.conf
31
     */
32
    public function generate(){
33
        $conf = "[globals] \n";
34
        $conf.= "TRANSFER_CONTEXT=internal-transfer; \n";
35
        foreach ($this->arrObject as $appClass) {
36
            $conf.= $appClass->extensionGlobals();
37
        }
38
        $conf.= "\n";
39
        $conf.= "\n";
40
        $conf.= "[general] \n";
41
        $conf.= "\n";
42
43
        // Создаем диалплан внутренних учеток.
44
        $this->generateOtherExten($conf);
45
        // Контекст для внутренних вызовов.
46
        $this->generateInternal($conf);
47
        // Контекст для внутренних переадресаций.
48
        $this->generateInternalTransfer($conf);
49
        // Создаем контекст хинтов.
50
        $this->generateSipHints($conf);
51
        // Создаем контекст (исходящие звонки).
52
        $this->generateOutContextPeers($conf);
53
        // Описываем контекст для публичных входящих.
54
        $this->generatePublicContext($conf);
55
        // Переключатель по времени.
56
        $this->generateOutWorkTimes($conf);
57
58
        Util::file_write_content("/etc/asterisk/extensions.conf", $conf);
59
    }
60
61
    /**
62
     * Генератор контекста для внутренних вызовов.
63
     * @param $conf
64
     */
65
    private function generateInternal(&$conf){
66
        $extension = 'X!';
67
        $technology = p_SIP::get_technology();
68
69
        foreach ($this->arrObject as $appClass) {
70
            $conf.= $appClass->extensionGenContexts();
71
        }
72
        $conf.= "\n";
73
        $conf.= "[internal-num-undefined] \n";
74
        $conf.= 'exten => _'.$extension.',1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\t";
75
        $conf.= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))'."\n\t";
76
        $conf.= "same => n,Playback(pbx-invalid,noanswer) \n\n";
77
78
        $conf.= "[internal-fw]\n";
79
        $conf.= 'exten => _'.$extension.',1,NoOp(DIALSTATUS - ${DIALSTATUS})'."\n\t";
80
        // CANCEL - вызов был отменен, к примеру *0, не нужно дальше искать адресат.
81
        $conf.= 'same => n,ExecIf($["${DIALSTATUS}" == "CANCEL"]?Hangup())'."\n\t";
82
        // BUSY - занято. К примру абонент завершил вызов или DND.
83
        $conf.= 'same => n,ExecIf($["${DIALSTATUS}" == "BUSY"]?Set(dstatus=FW_BUSY))'."\n\t";
84
        // CHANUNAVAIL - канал не доступен. К примеру телефон не зарегистрирован или не отвечает.
85
        $conf.= 'same => n,ExecIf($["${DIALSTATUS}" == "CHANUNAVAIL"]?Set(dstatus=FW_UNAV))'."\n\t";
86
        // NOANSWER - не ответили по таймауту.
87
        $conf.= 'same => n,ExecIf($["${dstatus}x" == "x"]?Set(dstatus=FW))'."\n\t";
88
        $conf.= 'same => n,Set(fw=${DB(${dstatus}/${EXTEN})})'."\n\t";
89
        $conf.= 'same => n,ExecIf($["${fw}x" != "x"]?Set(__pt1c_UNIQUEID=${UNDEFINED})'."\n\t";
90
        $conf.= 'same => n,ExecIf($["${fw}x" != "x"]?Goto(internal,${fw},1))'."\n\t";
91
        $conf.= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))'."\n\t";
92
        $conf.= 'same => n,Hangup() '."\n\n";
93
94
        $conf.= "[all_peers]\n";
95
        $conf.= 'include => internal-hints'."\n";
96
        $conf.= 'exten => failed,1,Hangup()'."\n";
97
98
        $conf.= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())'."\n\t";
99
        $conf.= 'same => n,Set(__FROM_CHAN=${CHANNEL})'."\n\t";
100
        $conf.= 'same => n,ExecIf($["${OLD_LINKEDID}x" == "x"]?Set(__OLD_LINKEDID=${CDR(linkedid)}))'."\n\t";
101
        $conf.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))'."\n\t";
102
        $conf.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))'."\n\t";
103
104
        $conf.= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(num)=${FROM_PEER}))'."\n\t";
105
        $conf.= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(name)=${FROM_PEER}))'."\n\t";
106
107
        $conf.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local" && "${FROM_PEER}x" == "x"]?Set(__FROM_PEER=${CALLERID(num)}))'."\n\t";;
108
        $conf.= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'."\n\t";
109
        $conf.= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)'."\n\t";
110
111
        // Описываем возможность прыжка в пользовательский sub контекст.
112
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)'."\n\t";
113
114
        $conf.= 'same => n,Goto(peer_${FROM_PEER},${EXTEN},1)'."\n\n";
115
116
        $pickupexten = Config::get_pickupexten();
117
        $conf.= 'exten => _'.$pickupexten.$extension.',1,Set(PICKUPEER='.$technology.'/${FILTER(0-9,${EXTEN:2})})'."\n\t";
118
        $conf.= 'same => n,Set(pt1c_dnid=${EXTEN})'."\n\t";
119
        $conf.= 'same => n,PickupChan(${PICKUPEER})'."\n\t";
120
        $conf.= 'same => n,Hangup()'."\n\n";
121
122
        $voicemail_exten = Config::get_voicemail_exten();
123
        $conf.= 'exten => '.$voicemail_exten.',1,NoOp(NOTICE, Dialing out from ${CALLERID(all)} to VoiceMail)'."\n\t";
124
        $conf.= 'same => n,VoiceMailMain(admin@voicemailcontext,s)'."\n\t";
125
        $conf.= 'same => n,Hangup()'."\n\n";
126
127
        $conf.= "[voice_mail_peer] \n";
128
        $conf.= 'exten => voicemail,1,Answer()'."\n\t";
129
        $conf.= 'same => n,VoiceMail(admin@voicemailcontext)'."\n\t";
130
        $conf.= 'same => n,Hangup()'."\n\n";
131
132
        // Контекст для внутренних вызовов.
133
        $conf.= "[internal] \n";
134
135
        foreach ($this->arrObject as $appClass) {
136
            $conf.= $appClass->getIncludeInternal();
137
        }
138
139
        foreach ($this->arrObject as $appClass) {
140
            $conf.= $appClass->extensionGenInternal();
141
        }
142
143
        $conf.= 'exten => i,1,NoOp(-- INVALID NUMBER --)'."\n\t";
144
        $conf.= 'same => n,Set(DIALSTATUS=INVALID_NUMBER)'."\n\t";
145
        $conf.= 'same => n,Playback(privacy-incorrect,noanswer)'."\n\t";
146
        $conf.= 'same => n,Hangup()'."\n";
147
148
        $conf.= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\n";
149
150
        $conf.= "[internal-incoming]\n";
151
        $conf.= 'exten => _.!,1,ExecIf($["${MASTER_CHANNEL(M_TIMEOUT)}x" != "x"]?Set(TIMEOUT(absolute)=${MASTER_CHANNEL(M_TIMEOUT)}))'." \n\t";
152
        $conf.= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT_CHANNEL)=${CHANNEL})'." \n\t";
153
        $conf.= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT)=${EMPTY_VAR})'." \n\t";
154
        $conf.= 'same => n,Goto(internal,${EXTEN},1)'." \n\n";
155
156
        $conf.= "[internal-users] \n";
157
        $conf.= 'exten => _'.$extension.',1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'." \n\t";
158
        $conf.= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Set(SIPADDHEADER01=${EMPTY_VAR})'." \n\t";
159
        $conf.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))'." \n\t";
160
161
        $conf.= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)'."\n\t";
162
        // Проверим, существует ли такой пир.
163
164
        if('SIP' === $technology){
165
            $conf.= 'same => n,ExecIf($["${SIPPEER(${EXTEN},status)}x" == "x"]?Goto(internal-num-undefined,${EXTEN},1))'." \n\t";
166
        }else{
167
            $conf.= 'same => n,ExecIf($["${PJSIP_ENDPOINT(${EXTEN},auth)}x" == "x"]?Goto(internal-num-undefined,${EXTEN},1))'." \n\t";
168
        }
169
        $conf.= 'same => n,ExecIf($["${DEVICE_STATE('.$technology.'/${EXTEN})}" == "BUSY"]?Set(DIALSTATUS=BUSY))'." \n\t";
170
        $conf.= 'same => n,GotoIf($["${DEVICE_STATE('.$technology.'/${EXTEN})}" == "BUSY"]?fw_start)'." \n\t";
171
172
        // Как долго звонить пиру.
173
        $conf.= 'same => n,Set(ringlength=${DB(FW_TIME/${EXTEN})})'." \n\t";
174
        $conf.= 'same => n,ExecIf($["${ringlength}x" == "x" || "${QUEUE_SRC_CHAN}x" != "x"]?Set(ringlength=600))'." \n\t";
175
176
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1) '." \n\t";
177
        // Совершаем вызов пира.
178
179
        $technology = p_SIP::get_technology();
180
        if($technology === 'SIP') {
181
            $conf.= 'same => n,Dial('.$technology.'/${EXTEN},${ringlength},TtekKHhM(dial_answer)b(dial_create_chan,s,1))'." \n\t";
182
        }else{
183
            // $conf.= 'same => n,Dial(${PJSIP_DIAL_CONTACTS(${EXTEN})},${ringlength},TtekKHhU(dial_answer)b(dial_create_chan,s,1))'." \n\t";
184
            $conf.= 'same => n,Set(DST_CONTACT=${PJSIP_DIAL_CONTACTS(${EXTEN})})'." \n\t";
185
            $conf.= 'same => n,ExecIf($["${DST_CONTACT}x" != "x"]?Dial(${DST_CONTACT},${ringlength},TtekKHhU(${ISTRANSFER}dial_answer)b(${ISTRANSFER}dial_create_chan,s,1)):Set(DIALSTATUS=CHANUNAVAIL))'." \n\t";
186
        }
187
188
189
        $conf.= 'same => n(fw_start),NoOp(dial_hangup)'." \n\t";
190
191
        // QUEUE_SRC_CHAN - установлена, если вызов сервершен агенту очереди.
192
        // Проверяем нужна ли переадресация
193
        $expression = '$["${DIALSTATUS}" != "ANSWER" && "${QUEUE_SRC_CHAN}x" == "x"]';
194
        $conf.= 'same => n,ExecIf('.$expression.'?Goto(internal-fw,${EXTEN},1))'." \n\t";
195
        $conf.= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))'." \n\t";
196
        $conf.= 'same => n,Hangup()'."\n\n";
197
198
        $conf.= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\n";
199
200
    }
201
202
    /**
203
     * Генератор контекста для переадресаций.
204
     * @param $conf
205
     */
206
    private function generateInternalTransfer(&$conf){
207
208
        $conf.= "[internal-transfer] \n";
209
210
        foreach ($this->arrObject as $appClass) {
211
            $conf.= $appClass->getIncludeInternalTransfer();
212
        }
213
214
        foreach ($this->arrObject as $appClass) {
215
            $conf.= $appClass->extensionGenInternalTransfer();
216
        }
217
        $conf.= 'exten => h,1,Gosub(transfer_dial_hangup,${EXTEN},1)'."\n\n";
218
    }
219
220
    /**
221
     * Генератор прочих контекстов.
222
     * @param $conf
223
     */
224
    private function generateOtherExten(&$conf){
225
        $extension = 'X!';
226
        // Контекст для AMI originate. Без него отображается не корректный CallerID.
227
        $conf.= '[sipregistrations]'."\n\n";
228
229
        $conf.= '[messages]'."\n".
230
            'exten => _'.$extension.',1,MessageSend(sip:${EXTEN},"${CALLERID(name)}"${MESSAGE(from)})'."\n\n";
231
232
        $conf.= '[internal-originate]'." \n";
233
        $conf.= 'exten => _'.$extension.',1,NoOP(Hint ${HINT} exten ${EXTEN} )'." \n";
234
        $conf.= '; Если это originate, то скроем один CDR.'." \n\t";
235
        $conf.= 'same => n,ExecIf($["${pt1c_cid}x" != "x"]?Set(CALLERID(num)=${pt1c_cid}))'." \n\t";
236
237
        $technology = p_SIP::get_technology();
238
        if('SIP' == $technology){
239
            $conf.= 'same => n,ExecIf($["${SIPADDHEADER}x" != "x"]?SIPaddheader(${SIPADDHEADER}))'." \n\t";
240
        }else{
241
            $conf.= 'same => n,ExecIf($["${CUT(CHANNEL,\;,2)}" == "2"]?Set(__PT1C_SIP_HEADER=${SIPADDHEADER}))'." \n\t";
242
        }
243
        $conf.= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Set(ADDITIONAL_PEER=&Local/${peer_mobile}@outgoing/n))'." \n\t";
244
245
        // Описываем возможность прыжка в пользовательский sub контекст.
246
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)'."\n\t";
247
        $conf.= 'same => n,Dial(Local/${EXTEN}@internal-users/n${ADDITIONAL_PEER},60,TteKkHhb(originate_create_chan,s,1))'." \n\n";
248
249
        $conf.= '[macro-dial_answer]'."\n";
250
        // $conf.= 'exten => s,1,AGI(cdr_connector.php,${ISTRANSFER}dial_answer)'."\n\n";
251
        $conf.= 'exten => s,1,Gosub(${ISTRANSFER}dial_answer,${EXTEN},1)'."\n\n";
252
253
        $conf.= '[originate_create_chan]'." \n";
254
        $conf.= 'exten => s,1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'."\n\t";
255
        $conf.= 'same => n,return'." \n\n";
256
257
        $conf.= '[dial_create_chan]'." \n";
258
        $conf.= 'exten => s,1,Gosub(lua_${ISTRANSFER}dial_create_chan,${EXTEN},1)'."\n\t";
259
        $conf.= 'same => n,Set(pt1c_is_dst=1)'." \n\t";
260
        $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";
261
        $conf.= 'same => n,Set(__PT1C_SIP_HEADER=${UNDEFINED})'." \n\t";
262
        $conf.= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'." \n\t";
263
        $conf.= 'same => n,return'." \n\n";
264
265
        $conf.= '[hangup_handler]'."\n";
266
        $conf.= 'exten => s,1,NoOp(--- hangup - ${CHANNEL} ---)'."\n\t";
267
        $conf.= 'same => n,Gosub(hangup_chan,${EXTEN},1)'."\n\t";
268
269
        $conf.= 'same => n,return'."\n\n";
270
271
        $conf.= '[set_orign_chan]'."\n";
272
        $conf.= 'exten => s,1,Wait(0.2)'."\n\t";
273
        $conf.= 'same => n,Set(pl=${IF($["${CHANNEL:-1}" == "1"]?2:1)})'."\n\t";
274
        $conf.= 'same => n,Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)})'."\n\t";
275
        $conf.= 'same => n,ExecIf($[ "${orign_chan}x" == "x" ]?Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},FROM_CHAN)}))'."\n\t";
276
        $conf.= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN}x" != "x" ]?Set(__QUEUE_SRC_CHAN=${orign_chan}))'."\n\t";
277
        $conf.= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN:0:5}" == "Local" ]?Set(__QUEUE_SRC_CHAN=${FROM_CHAN}))'."\n\t";
278
        $conf.= 'same => n,ExecIf($[ "${FROM_CHAN}x" == "x" ]?Set(__FROM_CHAN=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)}))'."\n\t";
279
        $conf.= 'same => n,return'."\n\n";
280
281
        $conf.= '[playback]'."\n";
282
        $conf.= 'exten => s,1,Playback(hello_demo,noanswer)'."\n\t";
283
        $conf.= 'same => n,ExecIf($["${SRC_BRIDGE_CHAN}x" == "x"]?Wait(30))'."\n\t";
284
        $conf.= 'same => n,Wait(0.3)'."\n\t";
285
        $conf.= 'same => n,Bridge(${SRC_BRIDGE_CHAN},kKTthH)'."\n\n";
286
287
        $conf.= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\n";
288
289
        // TODO / Добавление / удаление префиксов на входящий callerid.
290
        $conf.= '[add-trim-prefix-clid]'."\n";
291
        $conf.= 'exten => _.!,1,NoOp(--- Incoming call from ${CALLERID(num)} ---)'."\n\t";
292
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)'."\n\t";
293
        // Отсекаем "+".
294
        // $conf.= 'same => n,ExecIf( $["${CALLERID(num):0:1}" == "+"]?Set(CALLERID(num)=${CALLERID(num):1}))'."\n\t";
295
        // Отсекаем "7" и добавляем "8".
296
        // $conf.= 'same => n,ExecIf( $["${REGEX("^7[0-9]+" ${CALLERID(num)})}" == "1"]?Set(CALLERID(num)=8${CALLERID(num):1}))'."\n\t";
297
        $conf.= 'same => n,return'."\n\n";
298
299
    }
300
301
    /**
302
     * Генератор хинтов SIP.
303
     * @param $conf
304
     */
305
    private function generateSipHints(&$conf){
306
        $conf.= "[internal-hints] \n";
307
        foreach ($this->arrObject as $appClass) {
308
            $conf.= $appClass->extensionGenHints();
309
        }
310
        $conf.= "\n\n";
311
    }
312
313
    /**
314
     * Генератор исходящих контекстов.
315
     * @param $conf
316
     */
317
    private function generateOutContextPeers(&$conf){
318
        $conf.= "[outgoing] \n";
319
320
        $conf.= 'exten => _+.!,1,NoOp(Strip + sign from number and convert it to 00)'." \n\t";
321
        $conf.= 'same => n,Set(ADDPLUS=00);'." \n\t";
322
        $conf.= 'same => n,Goto(${CONTEXT},${EXTEN:1},1);'." \n\n";
323
        $conf.= 'exten => _X!,1,NoOp(Start outgoing calling...)'." \n\t";
324
        $conf.= 'same => n,Ringing()'." \n\t";
325
326
        // Описываем возможность прыжка в пользовательский sub контекст.
327
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)'."\n\t";
328
329
        /** @var Models\OutgoingRoutingTable $routs */
330
        /** @var Models\OutgoingRoutingTable $rout */
331
        $routs   = Models\OutgoingRoutingTable::find(['order' => 'priority']);
332
        $provider_contexts = [];
333
        foreach ($routs as $rout) {
334
            foreach ($this->arrObject as $appClass) {
335
                $technology = $appClass->getTechByID($rout->providerid);
336
                if($technology !== ''){
337
                    $rout_data = $rout->toArray();
338
                    $rout_data['technology'] = $technology;
339
                    $id_dialplan = $rout_data['providerid'].'-'.$rout_data['id'].'-outgoing';
340
                    $provider_contexts[$id_dialplan] = $rout_data;
341
                    $conf.= $this->generateOutgoingRegexPattern($rout_data);
342
                    break;
343
                }
344
            }
345
        }
346
        $conf.= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Hangup())'." \n\t";
347
        $conf.= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${BLINDTRANSFER}x" != "x" && "${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\t";
348
        $conf.= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))'." \n\t";
349
        // $conf.= 'same => n,ExecIf($["${ROUTFOUND}x" == "x"]?AGI(cdr_connector.php,dial))'." \n\t";
350
        $conf.= 'same => n,ExecIf($["${ROUTFOUND}x" == "x"]?Gosub(dial,${EXTEN},1))'."\n\t";
351
352
        $conf.= 'same => n,Playback(silence/2,noanswer)'." \n\t";
353
        $conf.= 'same => n,ExecIf($["${ROUTFOUND}x" != "x"]?Playback(followme/sorry,noanswer):Playback(cannot-complete-as-dialed,noanswer))'." \n\t";
354
        $conf.= 'same => n,Hangup()'." \n\n";
355
        // $conf.= 'exten => h,1,AGI(cdr_connector.php,${ISTRANSFER}dial_hangup)'."\n";
356
        $conf.= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\t";
357
358
        foreach ($provider_contexts as $id_dialplan => $rout){
359
            $conf.= "\n[{$id_dialplan}]\n";
360
            if(isset($rout['trimfrombegin']) && $rout['trimfrombegin'] > 0){
361
                // $exten_var = '${ADDPLUS}${EXTEN:'.$rout['trimfrombegin'].'}';
362
                $exten_var = '${EXTEN:'.$rout['trimfrombegin'].'}';
363
                $change_exten = 'same => n,ExecIf($["${EXTEN}" != "${number}"]?Goto(${CONTEXT},${number},$[${PRIORITY} + 1]))'."\n\t";
364
            }else{
365
                $exten_var = '${ADDPLUS}${EXTEN}';
366
                $change_exten = '';
367
            }
368
            $conf.= 'exten => _X!,1,Set(number='.$rout['prepend'].$exten_var.')'."\n\t";
369
            $conf.= $change_exten;
370
            foreach ($this->arrObject as $appClass) {
371
                $conf.= $appClass->generate_out_rout_context($rout);
372
            }
373
            $conf.= 'same => n,ExecIf($["${number}x" == "x"]?Hangup())'."\n\t";
374
            $conf.= 'same => n,Set(ROUTFOUND=1)'."\n\t";
375
            $conf.= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)'."\n\t";
376
377
            $conf.= 'same => n,ExecIf($["${EXTERNALPHONE}" == "${EXTEN}"]?Set(DOPTIONS=tk))'."\n\t";
378
379
            // Описываем возможность прыжка в пользовательский sub контекст.
380
            $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS('.$rout['providerid'].'-outgoing-custom,${EXTEN},1)}" == "1"]?'.$rout['providerid'].'-outgoing-custom,${EXTEN},1)'."\n\t";
381
382
            $technology = p_SIP::get_technology();
383
            if($technology === 'SIP') {
384
                $conf.= 'same => n,Dial('.$rout['technology'].'/'.$rout['providerid'].'/${number},600,${DOPTIONS}TKM(dial_answer)b(dial_create_chan,s,1))'."\n\t";
385
            }else{
386
                $conf.= 'same => n,Dial('.$rout['technology'].'/${number}@'.$rout['providerid'].',600,${DOPTIONS}TKU(dial_answer)b(dial_create_chan,s,1))'."\n\t";
387
            }
388
            foreach ($this->arrObject as $appClass) {
389
                $conf.= $appClass->generate_out_rout_after_dial_context($rout);
390
            }
391
            $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";
392
393
            $conf.= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))'."\n\t";
394
            $conf.= 'same => n,ExecIf($["${DIALSTATUS}" = "ANSWER"]?Hangup())'."\n\t";
395
            $conf.= 'same => n,Set(pt1c_UNIQUEID=${EMPTY_VALUE})'."\n\t";
396
            $conf.= 'same => n,return'."\n";
397
        }
398
    }
399
400
    /**
401
     * Генератор входящих контекстов.
402
     * @param string | array       $provider
403
     * @param string | array $login
404
     * @param string $uniqid
405
     * @return string
406
     */
407
    public static function generateIncomingContextPeers($provider, $login='', $uniqid='', $arrObject=NULL):string {
408
        $conf = '';
409
        $dialplan = [];
410
411
        if('none' === $provider){
412
            // Звонки по sip uri.
413
            $filter = [
414
                'provider IS NULL AND priority<>9999',
415
                'order' => 'provider,priority,extension',
416
            ];
417
        }else if(is_array($provider)){
418
            $filter = [
419
                'provider IN ({provider:array})',
420
                'bind'  => [
421
                    'provider'   => array_keys($provider),
422
                ],
423
                'order' => 'provider,priority,extension',
424
            ];
425
       }else{
426
            // Звонки через провайдера.
427
            $filter = [
428
                "provider = '$provider'",
429
                'order' => 'provider,priority,extension',
430
            ];
431
432
        }
433
        /** @var Models\IncomingRoutingTable $default_action */
434
        $default_action = Models\IncomingRoutingTable::findFirst('priority = 9999');
435
        /** @var Models\IncomingRoutingTable $m_data */
436
        $m_data = Models\IncomingRoutingTable::find($filter);
437
        $data = $m_data->toArray();
438
439
        $need_def_rout = true;
440
        foreach($data as $rout) {
441
            $number = trim($rout['number']);
442
            if($number === 'X!' || $number === ''){
443
                $need_def_rout = false;
444
                break;
445
            }
446
        }
447
        if($need_def_rout === true && 'none' !== $provider){
448
            $data[] = array('number' => '', 'extension' => '', 'timeout' => '');
449
        }
450
        $config = new Config();
451
        $lang   = str_replace('_', '-', $config->get_general_settings('PBXLanguage'));
452
453
        $rout_data_dial = [];
454
        foreach($data as $rout){
455
            $number      = trim($rout['number']);
456
            $timeout     = trim($rout['timeout']);
457
            $rout_number = ($number === '')?'X!':$number;
458
            $rout_data = &$dialplan[$rout_number];
459
            if(empty($rout_data)){
460
                $ext_prefix = ('none' === $provider)?'':'_';
461
                $rout_data.= "exten => {$ext_prefix}{$rout_number},1,NoOp(--- Incoming call ---)\n\t";
462
                $rout_data.= 'same => n,Set(CHANNEL(language)='.$lang.')'."\n\t";
463
                $rout_data.= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)'."\n\t";
464
                $rout_data.= 'same => n,Set(__FROM_DID=${EXTEN})'."\n\t";
465
                $rout_data.= 'same => n,Set(__FROM_CHAN=${CHANNEL})'."\n\t";
466
467
                // Установка имени пира.
468
                $rout_data.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))'."\n\t";
469
                $rout_data.= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))'."\n\t";
470
471
                // TODO / Подмена входящего callerid.
472
                $rout_data.= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)'."\n\t";
473
474
                if($arrObject){
475
                    foreach ($arrObject as $appClass) {
476
                        $rout_data.= $appClass->generate_incoming_rout_before_dial($rout_number);
477
                    }
478
                }
479
480
                // Перехват на ответственного.
481
                $rout_data.= 'same => n,UserEvent(Interception,CALLERID: ${CALLERID(num)},chan1c: ${CHANNEL},FROM_DID: ${FROM_DID})'."\n\t";
482
                // Проверим распискние для входящих внешних звонков.
483
                $rout_data.= 'same => n,Gosub(check-out-work-time,${EXTEN},1)';
484
                // Описываем возможность прыжка в пользовательский sub контекст.
485
                $rout_data.= " \n\t".'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)';
486
            }
487
488
            if(!empty($rout['extension'])){
489
                $rout_data = rtrim($rout_data);
490
                // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
491
                // При ответе может отработать следующий приоритет.
492
                if(!isset($rout_data_dial[$rout_number])){
493
                    $rout_data_dial[$rout_number] = '';
494
                }
495
496
                $dial_command = " \n\t".'same => n,'.'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?'."Dial(Local/{$rout['extension']}@internal-incoming/n,{$timeout},TKg));";
497
                $rout_data_dial[$rout_number].= " \n\t"."same => n,Set(M_TIMEOUT={$timeout})";
498
                $rout_data_dial[$rout_number].= $dial_command;
499
500
                if(is_array($provider)){
501
                    $key = $provider[$rout['provider']]??'';
502
                    if(!isset($rout_data_dial[$key])){
503
                        $rout_data_dial[$key] = '';
504
                    }
505
                    if(empty($number)){
506
                        $rout_data_dial[$key].= $dial_command;
507
                    }
508
                }
509
            }
510
        }
511
512
        if(is_string($login)){
513
            $add_login_pattern = !empty($login);
514
            foreach($data as $rout){
515
                if(!$add_login_pattern)break; // Логин не заполнен, обработка не требуется.
516
                $is_num = preg_match_all('/^\d+$/m', $login, $matches, PREG_SET_ORDER, 0);
517
                if($is_num === 1){
518
                    // Это числовой номер, потому, не требуется дополнительно описывать exten.
519
                    $add_login_pattern = false;
520
                    break;
521
                }
522
                if(trim($rout['number']) !== $login){
523
                    // Совпадение exten не найдено. Идем дальше.
524
                    continue;
525
                }
526
                // Совпадение найдено, не требуется дополнительно описывать exten.
527
                $add_login_pattern = false;
528
                break;
529
            }
530
            if($add_login_pattern && array_key_exists('X!',$rout_data_dial) && isset($dialplan['X!']) ){
531
                $dialplan[$login]       = str_replace('_X!,1', "{$login},1", $dialplan['X!']);
532
                $rout_data_dial[$login] = $rout_data_dial['X!'];
533
            }
534
        }else{
535
            foreach (array_values($provider) as $_login){
0 ignored issues
show
Bug introduced by
It seems like $provider can also be of type string; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

535
            foreach (array_values(/** @scrutinizer ignore-type */ $provider) as $_login){
Loading history...
536
                $dialplan[$_login] = str_replace('_X!,1', "{$_login},1", $dialplan['X!']);
537
            }
538
        }
539
540
        foreach ($dialplan as $key => &$dpln){
541
            if(!array_key_exists($key,$rout_data_dial)){
542
                continue;
543
            }
544
            $dpln = rtrim($dpln);
545
            $dpln.= $rout_data_dial[$key];
546
        }
547
        unset($dpln);
548
549
        $uniqid = is_string($provider)? $provider : $uniqid;
550
        $conf.= "\n"."[{$uniqid}-incoming]\n";
551
        foreach($dialplan as $dpln){
552
            $conf .= $dpln."\n";
553
            if(null == $default_action && 'none' !== $provider) continue;
554
            if('extension' === $default_action->action){
555
                // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
556
                // При ответе может отработать следующий приоритет.
557
                $conf .= "\t".'same => n,Set(M_TIMEOUT=0)'."\n";
558
                $conf .= "\t"."same => n,".'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?'."Dial(Local/{$default_action->extension}@internal/n,,TKg)); default action"."\n";
559
560
                if($arrObject){
561
                    foreach ($arrObject as $appClass) {
562
                        $conf.= $appClass->generate_incoming_rout_after_dial_context($uniqid);
563
                    }
564
                }
565
                $conf.= " \t".'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)'."\n";
566
            }else if('busy' === $default_action->action){
567
                $conf .= "\t"."same => n,Busy()"."\n";
568
            }
569
            $conf .= "\t"."same => n,Hangup()"."\n";
570
        }
571
572
        return $conf;
573
574
    }
575
576
    /**
577
     * Генератор исходящего маршрута.
578
     * @param $rout
579
     * @return string
580
     */
581
    private function generateOutgoingRegexPattern($rout){
582
        $conf = '';
583
        $restnumbers = '';
584
        if(isset($rout['restnumbers']) && $rout['restnumbers']>0){
585
            $restnumbers = "[0-9]{".$rout['restnumbers']."}$";
586
        }elseif($rout['restnumbers'] == 0){
587
            $restnumbers = "$";
588
        }elseif($rout['restnumbers'] == -1){
589
            $restnumbers = "";
590
        }
591
        $numberbeginswith = $rout['numberbeginswith'];
592
        $conf.= 'same => n,ExecIf($["${REGEX("^'.$numberbeginswith.$restnumbers.'" ${EXTEN})}" == "1"]?Gosub('.$rout['providerid'].'-'.$rout['id'].'-outgoing,${EXTEN},1))'." \n\t";
593
        return $conf;
594
    }
595
    /**
596
     * Контекст для входящих внешних звонков без авторизации.
597
     * @param $conf
598
     */
599
    private function generatePublicContext(&$conf){
600
        $conf.= "\n";
601
        $conf.= self::generateIncomingContextPeers('none', '', '', $this->arrObject);
602
        $conf.= "[public-direct-dial] \n";
603
        foreach ($this->arrObject as $appClass) {
604
            $conf.= $appClass->generatePublicContext();
605
        }
606
        $filter = ["provider IS NULL AND priority<>9999"];
607
608
        /**
609
         * @var array
610
         */
611
        $m_data = Models\IncomingRoutingTable::find($filter);
612
        if(count($m_data->toArray())>0){
613
            $conf.= 'include => none-incoming';
614
        }
615
    }
616
617
    /**
618
     * Описываем нерабочее время.
619
     * @param $conf
620
     * @return string
621
     */
622
    private function generateOutWorkTimes(&$conf){
623
        $conf.= "\n\n[playback-exit]\n";
624
        $conf.= 'exten => _.!,1,NoOp(check time)'."\n\t";
625
        $conf.= 'same => n,Gosub(dial_outworktimes,${EXTEN},1)'."\n\t";
626
        $conf.= 'same => n,Playback(${filename})'."\n\t";
627
        $conf.= 'same => n,Hangup()'."\n\n";
628
629
        $conf.= "[check-out-work-time]\n";
630
        $conf.= "exten => _.!,1,NoOp(check time)\n\t";
631
632
        $data = Models\OutWorkTimes::find(['order'=>'date_from']);
633
634
        $conf_out_set_var = '';
635
636
        $now_year       = 1*date('Y', time());
637
        foreach ($data as $out_data){
638
            /** @var Models\OutWorkTimes $out_data */
639
            if(!empty($out_data->date_from) && !empty($out_data->date_to)){
640
                $year_from = 1*date('Y', $out_data->date_from);
0 ignored issues
show
Bug introduced by
$out_data->date_from of type string is incompatible with the type integer expected by parameter $timestamp of date(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

640
                $year_from = 1*date('Y', /** @scrutinizer ignore-type */ $out_data->date_from);
Loading history...
641
                $year_to   = 1*date('Y', $out_data->date_to);
642
                if($now_year < $year_from || $now_year > $year_to){
643
                    // Правило не актуально для текущего года.
644
                    continue;
645
                }
646
            }
647
            $time_from  = $out_data->time_from;
648
            $time_to    = $out_data->time_to;
649
            if(empty($time_from) && empty($time_to)){
650
                $times = '*';
651
            }else{
652
                $time_to   = (empty($time_to))?'23:59':$time_to;
653
                $time_to   = (strlen($time_to) == 4)?"0{$time_to}":$time_to;
654
655
                $time_from = (empty($time_from))?'00:00':$time_from;
656
                $time_from = (strlen($time_from) == 4)?"0{$time_from}":$time_from;
657
658
                $times = "{$time_from}-{$time_to}";
659
            }
660
661
            $weekday_from = $out_data->weekday_from;
662
            $weekday_to   = $out_data->weekday_to;
663
            $arr_weekday  = array(null ,"mon", "tue", "wed", "thu", "fri", "sat", "sun");
664
            if(empty($weekday_from) && empty($weekday_to)){
665
                $weekdays = '*';
666
            }else{
667
                $weekday_from = (empty($weekday_from))?'1':$weekday_from;
668
                $weekday_to   = (empty($weekday_to))?'7':$weekday_to;
669
                $weekdays = "{$arr_weekday[$weekday_from]}-{$arr_weekday[$weekday_to]}";
670
            }
671
672
            $date_from  = $out_data->date_from;
673
            $date_to    = $out_data->date_to;
674
            if(empty($date_from)){
675
                $mdays  = '*';
676
                $months = '*';
677
            }else{
678
                $mdays = strtolower( date("j", $date_from) );
679
                $months= strtolower( date("M", $date_from) );
680
                if(!empty($date_to)){
681
                    $mdays  .= "-".strtolower( date("j", $date_to) );
682
                    $months .= "-".strtolower( date("M", $date_to) );
683
                }
684
            }
685
686
687
            if('extension' === $out_data->action){
688
                $appname = 'GotoIfTime';
689
                $appdata = "internal,{$out_data->extension},1";
690
            }else{
691
                /** @var Models\SoundFiles $res */
692
                $res = Models\SoundFiles::findFirst($out_data->audio_message_id);
693
                $audio_message = ($res == null)?'':Util::trim_extension_file($res->path);
694
695
                $conf_out_set_var .= "[work-time-set-var-{$out_data->id}]\n".
696
                                     'exten => _.!,1,Set(filename='.$audio_message.')'."\n\t".
697
                                     'same => n,Goto(playback-exit,${EXTEN},1)'."\n\n";
698
699
                $appname = 'ExecIfTime';
700
                $appdata = 'Goto(work-time-set-var-'.$out_data->id.',${EXTEN},1)';
701
            }
702
            $conf.= "same => n,{$appname}($times,$weekdays,$mdays,$months?{$appdata})\n\t";
703
        }
704
705
        $conf.="same => n,return\n\n";
706
707
        $conf.=$conf_out_set_var;
708
        return $conf;
709
    }
710
}