Passed
Push — develop ( 4e97fa...531ff7 )
by Портнов
05:02 queued 10s
created

ExtensionsConf::generateIncomingContextPeers()   F

Complexity

Conditions 45
Paths > 20000

Size

Total Lines 189
Code Lines 122

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 0
Metric Value
eloc 122
c 4
b 0
f 0
dl 0
loc 189
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\Modules\Config\ConfigClass;
25
use MikoPBX\Core\System\{MikoPBXConfig, Storage, Util};
26
use Phalcon\Di;
27
28
class ExtensionsConf extends ConfigClass
29
{
30
    protected string $description = 'extensions.conf';
31
32
    /**
33
     * Sorts array by priority field
34
     *
35
     * @param $a
36
     * @param $b
37
     *
38
     * @return int|null
39
     */
40
    public static function sortArrayByPriority(array $a, array $b): int
41
    {
42
        $aPriority = (int)($a['priority'] ?? 0);
43
        $bPriority = (int)($b['priority'] ?? 0);
44
        if ($aPriority === $bPriority) {
45
            return 0;
46
        }
47
48
        return ($aPriority < $bPriority) ? -1 : 1;
49
    }
50
51
    /**
52
     * Основной генератор extensions.conf
53
     */
54
    protected function generateConfigProtected(): void
55
    {
56
        /** @scrutinizer ignore-call */
57
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
58
        $conf              = "[globals] \n" .
59
            "TRANSFER_CONTEXT=internal-transfer; \n";
60
        if ($this->generalSettings['PBXRecordCalls'] === '1') {
61
            $conf .= "MONITOR_DIR=" . Storage::getMonitorDir() . " \n";
62
            $conf .= "MONITOR_STEREO=" . $this->generalSettings['PBXSplitAudioThread'] . " \n";
63
        }
64
        foreach ($additionalModules as $appClass) {
65
            $addition = $appClass->extensionGlobals();
66
            if ( ! empty($addition)) {
67
                $conf .= $appClass->confBlockWithComments($addition);
68
            }
69
        }
70
        $conf .= "\n";
71
        $conf .= "\n";
72
        $conf .= "[general] \n";
73
        $conf .= "\n";
74
75
        // Создаем диалплан внутренних учеток.
76
        $this->generateOtherExten($conf);
77
        // Контекст для внутренних вызовов.
78
        $this->generateInternal($conf);
79
        // Контекст для внутренних переадресаций.
80
        $this->generateInternalTransfer($conf);
81
        // Создаем контекст хинтов.
82
        $this->generateSipHints($conf);
83
        // Создаем контекст (исходящие звонки).
84
        $this->generateOutContextPeers($conf);
85
        // Описываем контекст для публичных входящих.
86
        $this->generatePublicContext($conf);
87
        // Переключатель по времени.
88
        $this->generateOutWorkTimes($conf);
89
90
        Util::fileWriteContent($this->config->path('asterisk.astetcdir') . '/extensions.conf', $conf);
91
    }
92
93
    /**
94
     * Генератор прочих контекстов.
95
     *
96
     * @param $conf
97
     */
98
    private function generateOtherExten(&$conf): void
99
    {
100
        $extension = 'X!';
101
        // Контекст для AMI originate. Без него отображается не корректный CallerID.
102
        $conf .= '[sipregistrations]' . "\n\n";
103
104
        $conf .= '[messages]' . "\n" .
105
            'exten => _' . $extension . ',1,MessageSend(sip:${EXTEN},"${CALLERID(name)}"${MESSAGE(from)})' . "\n\n";
106
107
        $conf .= '[internal-originate]' . " \n";
108
        $conf .= 'exten => _' . $extension . ',1,NoOP(Hint ${HINT} exten ${EXTEN} )' . " \n";
109
        $conf .= '; Если это originate, то скроем один CDR.' . " \n\t";
110
        $conf .= 'same => n,ExecIf($["${pt1c_cid}x" != "x"]?Set(CALLERID(num)=${pt1c_cid}))' . " \n\t";
111
112
        $conf .= 'same => n,ExecIf($["${CUT(CHANNEL,\;,2)}" == "2"]?Set(__PT1C_SIP_HEADER=${SIPADDHEADER}))' . " \n\t";
113
        $conf .= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Set(ADDITIONAL_PEER=&Local/${peer_mobile}@outgoing/n))' . " \n\t";
114
115
        // Описываем возможность прыжка в пользовательский sub контекст.
116
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
117
        $conf .= 'same => n,Dial(Local/${EXTEN}@internal-users/n${ADDITIONAL_PEER},60,TteKkHhb(originate_create_chan,s,1))' . " \n\n";
118
119
        $conf .= '[originate_create_chan]' . " \n";
120
        $conf .= 'exten => s,1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
121
        $conf .= 'same => n,return' . " \n\n";
122
123
        $conf .= '[dial_create_chan]' . " \n";
124
        $conf .= 'exten => s,1,Gosub(lua_${ISTRANSFER}dial_create_chan,${EXTEN},1)' . "\n\t";
125
        $conf .= 'same => n,Set(pt1c_is_dst=1)' . " \n\t";
126
        $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";
127
        $conf .= 'same => n,Set(__PT1C_SIP_HEADER=${UNDEFINED})' . " \n\t";
128
        $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t";
129
        $conf .= 'same => n,return' . " \n\n";
130
131
        $conf .= '[hangup_handler]' . "\n";
132
        $conf .= 'exten => s,1,NoOp(--- hangup - ${CHANNEL} ---)' . "\n\t";
133
        $conf .= 'same => n,Gosub(hangup_chan,${EXTEN},1)' . "\n\t";
134
135
        $conf .= 'same => n,return' . "\n\n";
136
137
        $conf .= '[set_orign_chan]' . "\n";
138
        $conf .= 'exten => s,1,Wait(0.2)' . "\n\t";
139
        $conf .= 'same => n,Set(pl=${IF($["${CHANNEL:-1}" == "1"]?2:1)})' . "\n\t";
140
        $conf .= 'same => n,Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)})' . "\n\t";
141
        $conf .= 'same => n,ExecIf($[ "${orign_chan}x" == "x" ]?Set(orign_chan=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},FROM_CHAN)}))' . "\n\t";
142
        $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN}x" != "x" ]?Set(__QUEUE_SRC_CHAN=${orign_chan}))' . "\n\t";
143
        $conf .= 'same => n,ExecIf($[ "${QUEUE_SRC_CHAN:0:5}" == "Local" ]?Set(__QUEUE_SRC_CHAN=${FROM_CHAN}))' . "\n\t";
144
        $conf .= 'same => n,ExecIf($[ "${FROM_CHAN}x" == "x" ]?Set(__FROM_CHAN=${IMPORT(${CUT(CHANNEL,\;,1)}\;${pl},BRIDGEPEER)}))' . "\n\t";
145
        $conf .= 'same => n,return' . "\n\n";
146
147
        $conf .= '[playback]' . "\n";
148
        $conf .= 'exten => s,1,Playback(hello_demo,noanswer)' . "\n\t";
149
        $conf .= 'same => n,ExecIf($["${SRC_BRIDGE_CHAN}x" == "x"]?Wait(30))' . "\n\t";
150
        $conf .= 'same => n,Wait(0.3)' . "\n\t";
151
        $conf .= 'same => n,Bridge(${SRC_BRIDGE_CHAN},kKTthH)' . "\n\n";
152
153
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
154
155
        // TODO / Добавление / удаление префиксов на входящий callerid.
156
        $conf .= '[add-trim-prefix-clid]' . "\n";
157
        $conf .= 'exten => _.!,1,NoOp(--- Incoming call from ${CALLERID(num)} ---)' . "\n\t";
158
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
159
        // Отсекаем "+".
160
        // $conf.= 'same => n,ExecIf( $["${CALLERID(num):0:1}" == "+"]?Set(CALLERID(num)=${CALLERID(num):1}))'."\n\t";
161
        // Отсекаем "7" и добавляем "8".
162
        // $conf.= 'same => n,ExecIf( $["${REGEX("^7[0-9]+" ${CALLERID(num)})}" == "1"]?Set(CALLERID(num)=8${CALLERID(num):1}))'."\n\t";
163
        $conf .= 'same => n,return' . "\n\n";
164
    }
165
166
    /**
167
     * Генератор контекста для внутренних вызовов.
168
     *
169
     * @param $conf
170
     */
171
    private function generateInternal(&$conf): void
172
    {
173
        $extension  = 'X!';
174
        $technology = SIPConf::getTechnology();
175
176
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
177
        foreach ($additionalModules as $appClass) {
178
            $addition = $appClass->extensionGenContexts();
179
            if ( ! empty($addition)) {
180
                $conf .= $appClass->confBlockWithComments($addition);
181
            }
182
        }
183
        $conf .= "\n";
184
        $conf .= "[internal-num-undefined] \n";
185
        $conf .= 'exten => _' . $extension . ',1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
186
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t";
187
        $conf .= "same => n,Playback(pbx-invalid,noanswer) \n\n";
188
189
        $conf .= "[internal-fw]\n";
190
        $conf .= 'exten => _' . $extension . ',1,NoOp(DIALSTATUS - ${DIALSTATUS})' . "\n\t";
191
        // CANCEL - вызов был отменен, к примеру *0, не нужно дальше искать адресат.
192
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CANCEL"]?Hangup())' . "\n\t";
193
        // BUSY - занято. К примру абонент завершил вызов или DND.
194
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "BUSY"]?Set(dstatus=FW_BUSY))' . "\n\t";
195
        // CHANUNAVAIL - канал не доступен. К примеру телефон не зарегистрирован или не отвечает.
196
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" == "CHANUNAVAIL"]?Set(dstatus=FW_UNAV))' . "\n\t";
197
        // NOANSWER - не ответили по таймауту.
198
        $conf .= 'same => n,ExecIf($["${dstatus}x" == "x"]?Set(dstatus=FW))' . "\n\t";
199
        $conf .= 'same => n,Set(fw=${DB(${dstatus}/${EXTEN})})' . "\n\t";
200
        $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Set(__pt1c_UNIQUEID=${UNDEFINED})' . "\n\t";
201
        $conf .= 'same => n,ExecIf($["${fw}x" != "x"]?Goto(internal,${fw},1))' . "\n\t";
202
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . "\n\t";
203
        $conf .= 'same => n,Hangup() ' . "\n\n";
204
205
        $conf .= "[all_peers]\n";
206
        $conf .= 'include => internal-hints' . "\n";
207
        $conf .= 'exten => failed,1,Hangup()' . "\n";
208
209
        $conf .= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())' . "\n\t";
210
        // Фильтр спецсимволов. Разершаем только цифры.
211
        $conf .= 'same => n,Set(cleanNumber=${FILTER(\*\#\+1234567890,${EXTEN})})' . "\n\t";
212
        $conf .= 'same => n,ExecIf($["${EXTEN}" != "${cleanNumber}"]?Goto(${CONTEXT},${cleanNumber},$[${PRIORITY} + 1]))' . "\n\t";
213
214
        $conf .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
215
        $conf .= 'same => n,ExecIf($["${OLD_LINKEDID}x" == "x"]?Set(__OLD_LINKEDID=${CHANNEL(linkedid)}))' . "\n\t";
216
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
217
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . "\n\t";
218
219
        $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(num)=${FROM_PEER}))' . "\n\t";
220
        $conf .= 'same => n,ExecIf($["${CALLERID(num)}x" == "x"]?Set(CALLERID(name)=${FROM_PEER}))' . "\n\t";
221
222
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local" && "${FROM_PEER}x" == "x"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
223
        $conf .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
224
        $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
225
226
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
227
        $dialplanNames = ['applications', 'internal', 'outgoing'];
228
        foreach ($dialplanNames as $name){
229
            $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS('.$name.',${EXTEN},1)}" == "1"]?'.$name.',${EXTEN},1)'." \n\t";
230
        }
231
        $conf .= 'same => n,Hangup()'." \n";
232
233
        $pickupexten  = $this->generalSettings['PBXFeaturePickupExten'];
234
        $conf        .= 'exten => _' . $pickupexten . $extension . ',1,Set(PICKUPEER=' . $technology . '/${FILTER(0-9,${EXTEN:2})})' . "\n\t";
235
        $conf        .= 'same => n,Set(pt1c_dnid=${EXTEN})' . "\n\t";
236
        $conf        .= 'same => n,PickupChan(${PICKUPEER})' . "\n\t";
237
        $conf        .= 'same => n,Hangup()' . "\n\n";
238
239
        $voicemail_exten  = $this->generalSettings['VoicemailExten'];
240
        $conf            .= 'exten => ' . $voicemail_exten . ',1,NoOp(NOTICE, Dialing out from ${CALLERID(all)} to VoiceMail)' . "\n\t";
241
        $conf            .= 'same => n,VoiceMailMain(admin@voicemailcontext,s)' . "\n\t";
242
        $conf            .= 'same => n,Hangup()' . "\n\n";
243
244
        $conf .= "[voice_mail_peer] \n";
245
        $conf .= 'exten => voicemail,1,Answer()' . "\n\t";
246
        $conf .= 'same => n,VoiceMail(admin@voicemailcontext)' . "\n\t";
247
        $conf .= 'same => n,Hangup()' . "\n\n";
248
249
        // Контекст для внутренних вызовов.
250
        $conf .= "[internal] \n";
251
252
        foreach ($additionalModules as $appClass) {
253
            $addition = $appClass->getIncludeInternal();
254
            if ( ! empty($addition)) {
255
                $conf .= $appClass->confBlockWithComments($addition);
256
            }
257
        }
258
259
        foreach ($additionalModules as $appClass) {
260
            $addition = $appClass->extensionGenInternal();
261
            if ( ! empty($addition)) {
262
                $conf .= $appClass->confBlockWithComments($addition);
263
            }
264
        }
265
266
        $conf .= 'exten => i,1,NoOp(-- INVALID NUMBER --)' . "\n\t";
267
        $conf .= 'same => n,Set(DIALSTATUS=INVALID_NUMBER)' . "\n\t";
268
        $conf .= 'same => n,Playback(privacy-incorrect,noanswer)' . "\n\t";
269
        $conf .= 'same => n,Hangup()' . "\n";
270
271
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
272
273
        $conf .= "[internal-incoming]\n";
274
        $conf .= 'exten => _.!,1,ExecIf($["${MASTER_CHANNEL(M_TIMEOUT)}x" != "x"]?Set(TIMEOUT(absolute)=${MASTER_CHANNEL(M_TIMEOUT)}))' . " \n\t";
275
        $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT_CHANNEL)=${CHANNEL})' . " \n\t";
276
        $conf .= 'same => n,Set(MASTER_CHANNEL(M_TIMEOUT)=${EMPTY_VAR})' . " \n\t";
277
        $conf .= 'same => n,Goto(internal,${EXTEN},1)' . " \n\n";
278
279
        $conf .= "[internal-users] \n";
280
        $conf .= 'exten => _' . $extension . ',1,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . " \n\t";
281
        $conf .= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Set(SIPADDHEADER01=${EMPTY_VAR})' . " \n\t";
282
        $conf .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Gosub(set_orign_chan,s,1))' . " \n\t";
283
284
        $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
285
        // Проверим, существует ли такой пир.
286
287
        $conf .= 'same => n,ExecIf($["${PJSIP_ENDPOINT(${EXTEN},auth)}x" == "x"]?Goto(internal-num-undefined,${EXTEN},1))' . " \n\t";
288
        $conf .= 'same => n,ExecIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?Set(DIALSTATUS=BUSY))' . " \n\t";
289
        $conf .= 'same => n,GotoIf($["${DEVICE_STATE(' . $technology . '/${EXTEN})}" == "BUSY"]?fw_start)' . " \n\t";
290
291
        // Как долго звонить пиру.
292
        $conf .= 'same => n,Set(ringlength=${DB(FW_TIME/${EXTEN})})' . " \n\t";
293
        $conf .= 'same => n,ExecIf($["${ringlength}x" == "x"]?Set(ringlength=600))' . " \n\t";
294
        $conf .= 'same => n,ExecIf($["${QUEUE_SRC_CHAN}x" != "x" && "${ISTRANSFER}x" == "x"]?Set(ringlength=600))' . " \n\t";
295
296
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1) ' . " \n\t";
297
        // Совершаем вызов пира.
298
        $conf .= 'same => n,Set(DST_CONTACT=${PJSIP_DIAL_CONTACTS(${EXTEN})})' . " \n\t";
299
        $conf .= 'same => n,ExecIf($["${FIELDQTY(DST_CONTACT,&)}" != "1"]?Set(__PT1C_SIP_HEADER=${EMPTY_VAR}))' . " \n\t";
300
        $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";
301
        $conf .= 'same => n(fw_start),NoOp(dial_hangup)' . " \n\t";
302
303
        // QUEUE_SRC_CHAN - установлена, если вызов сервершен агенту очереди.
304
        // Проверяем нужна ли переадресация
305
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${ISTRANSFER}x" != "x"]?Goto(internal-fw,${EXTEN},1))' . " \n\t";
306
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${QUEUE_SRC_CHAN}x" == "x"]?Goto(internal-fw,${EXTEN},1))' . " \n\t";
307
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . " \n\t";
308
        $conf .= 'same => n,Hangup()' . "\n\n";
309
310
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\n";
311
    }
312
313
    /**
314
     * Генератор контекста для переадресаций.
315
     *
316
     * @param $conf
317
     */
318
    private function generateInternalTransfer(&$conf): void
319
    {
320
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
321
        $conf              .= "[internal-transfer] \n";
322
323
        foreach ($additionalModules as $appClass) {
324
            $addition = $appClass->getIncludeInternalTransfer();
325
            if ( ! empty($addition)) {
326
                $conf .= $appClass->confBlockWithComments($addition);
327
            }
328
        }
329
330
        foreach ($additionalModules as $appClass) {
331
            $addition = $appClass->extensionGenInternalTransfer();
332
            if ( ! empty($addition)) {
333
                $conf .= $appClass->confBlockWithComments($addition);
334
            }
335
        }
336
        $conf .= 'exten => h,1,Gosub(transfer_dial_hangup,${EXTEN},1)' . "\n\n";
337
    }
338
339
    /**
340
     * Генератор хинтов SIP.
341
     *
342
     * @param $conf
343
     */
344
    private function generateSipHints(&$conf): void
345
    {
346
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
347
        $conf              .= "[internal-hints] \n";
348
        foreach ($additionalModules as $appClass) {
349
            $addition = $appClass->extensionGenHints();
350
            if ( ! empty($addition)) {
351
                $conf .= $appClass->confBlockWithComments($addition);
352
            }
353
        }
354
        $conf .= "\n\n";
355
    }
356
357
    /**
358
     * Генератор исходящих контекстов.
359
     *
360
     * @param $conf
361
     */
362
    private function generateOutContextPeers(&$conf): void
363
    {
364
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
365
        $conf              .= "[outgoing] \n";
366
367
        $conf .= 'exten => _+.!,1,NoOp(Strip + sign from number and convert it to +)' . " \n\t";
368
        $conf .= 'same => n,Set(ADDPLUS=+);' . " \n\t";
369
        $conf .= 'same => n,Goto(${CONTEXT},${EXTEN:1},1);' . " \n\n";
370
        $conf .= 'exten => _.!,1,ExecIf($[ "${EXTEN}" == "h" ]?Hangup())' . " \n\t";
371
        $conf .= 'same => n,Ringing()' . " \n\t";
372
373
        // Описываем возможность прыжка в пользовательский sub контекст.
374
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)' . "\n\t";
375
376
        /** @var OutgoingRoutingTable $routs */
377
        /** @var OutgoingRoutingTable $rout */
378
        $routs = OutgoingRoutingTable::find(['order' => 'priority'])->toArray();
379
        uasort($routs, __CLASS__ . '::sortArrayByPriority');
380
381
        $provider_contexts = [];
382
383
        foreach ($routs as $rout) {
384
            $technology = $this->getTechByID($rout['providerid']);
385
            if ($technology !== '') {
386
                $rout_data                       = $rout;
387
                $rout_data['technology']         = $technology;
388
                $id_dialplan                     = $rout_data['providerid'] . '-' . $rout_data['id'] . '-outgoing';
389
                $provider_contexts[$id_dialplan] = $rout_data;
390
                $conf                            .= $this->generateOutgoingRegexPattern($rout_data);
391
                continue;
392
            }
393
        }
394
        $conf .= 'same => n,ExecIf($["${peer_mobile}x" != "x"]?Hangup())' . " \n\t";
395
        $conf .= 'same => n,ExecIf($["${DIALSTATUS}" != "ANSWER" && "${BLINDTRANSFER}x" != "x" && "${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
396
        $conf .= 'same => n,ExecIf($["${BLINDTRANSFER}x" != "x"]?AGI(check_redirect.php,${BLINDTRANSFER}))' . " \n\t";
397
        $conf .= 'same => n,ExecIf($["${ROUTFOUND}x" == "x"]?Gosub(dial,${EXTEN},1))' . "\n\t";
398
399
        $conf .= 'same => n,Playback(silence/2,noanswer)' . " \n\t";
400
        $conf .= 'same => n,ExecIf($["${ROUTFOUND}x" != "x"]?Playback(followme/sorry,noanswer):Playback(cannot-complete-as-dialed,noanswer))' . " \n\t";
401
        $conf .= 'same => n,Hangup()' . " \n\n";
402
        $conf .= 'exten => h,1,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
403
404
        foreach ($provider_contexts as $id_dialplan => $rout) {
405
            $conf .= "\n[{$id_dialplan}]\n";
406
            $trimFromBegin = (int) ($rout['trimfrombegin']??0);
407
            if ($trimFromBegin > 0) {
408
                $exten_var    = '${ADDPLUS}${EXTEN:' . $rout['trimfrombegin'] . '}';
409
                $change_exten = 'same => n,ExecIf($["${EXTEN}" != "${number}"]?Goto(${CONTEXT},${number},$[${PRIORITY} + 1]))' . "\n\t";
410
            } else {
411
                $exten_var    = '${ADDPLUS}${EXTEN}';
412
                $change_exten = '';
413
            }
414
            $conf .= 'exten => _.!,1,Set(number=' . $rout['prepend'] . $exten_var . ')' . "\n\t";
415
            $conf .= 'same => n,Set(number=${FILTER(\*\#\+1234567890,${number})})' . "\n\t";
416
            $conf .= $change_exten;
417
            foreach ($additionalModules as $appClass) {
418
                $addition = $appClass->generateOutRoutContext($rout);
419
                if ( ! empty($addition)) {
420
                    $conf .= $appClass->confBlockWithComments($addition);
421
                }
422
            }
423
            $conf .= 'same => n,ExecIf($["${number}x" == "x"]?Hangup())' . "\n\t";
424
            $conf .= 'same => n,Set(ROUTFOUND=1)' . "\n\t";
425
            $conf .= 'same => n,Gosub(${ISTRANSFER}dial,${EXTEN},1)' . "\n\t";
426
427
            $conf .= 'same => n,ExecIf($["${EXTERNALPHONE}" == "${EXTEN}"]?Set(DOPTIONS=tk))' . "\n\t";
428
429
            // Описываем возможность прыжка в пользовательский sub контекст.
430
            $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS(' . $rout['providerid'] . '-outgoing-custom,${EXTEN},1)}" == "1"]?' . $rout['providerid'] . '-outgoing-custom,${EXTEN},1)' . "\n\t";
431
432
            if ($rout['technology'] === IAXConf::TYPE_IAX2) {
433
                $conf .= 'same => n,Dial(' . $rout['technology'] . '/' . $rout['providerid'] . '/${number},600,${DOPTIONS}TKU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1))' . "\n\t";
434
            } else {
435
                $conf .= 'same => n,Dial(' . $rout['technology'] . '/${number}@' . $rout['providerid'] . ',600,${DOPTIONS}TKU(${ISTRANSFER}dial_answer)b(dial_create_chan,s,1))' . "\n\t";
436
            }
437
            foreach ($additionalModules as $appClass) {
438
                $addition = $appClass->generateOutRoutAfterDialContext($rout);
439
                if ( ! empty($addition)) {
440
                    $conf .= $appClass->confBlockWithComments($addition);
441
                }
442
            }
443
            $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";
444
445
            $conf .= 'same => n,ExecIf($["${ISTRANSFER}x" != "x"]?Gosub(${ISTRANSFER}dial_hangup,${EXTEN},1))' . "\n\t";
446
            $conf .= 'same => n,ExecIf($["${DIALSTATUS}" = "ANSWER"]?Hangup())' . "\n\t";
447
            $conf .= 'same => n,Set(pt1c_UNIQUEID=${EMPTY_VALUE})' . "\n\t";
448
            $conf .= 'same => n,return' . "\n";
449
        }
450
    }
451
452
    /**
453
     * Генератор extension для контекста outgoing.
454
     *
455
     * @param string $uniqueID
456
     *
457
     * @return null|string
458
     */
459
    public function getTechByID(string $uniqueID): string
460
    {
461
        $technology = '';
462
        $provider   = Providers::findFirstByUniqid($uniqueID);
463
        if ($provider !== null) {
464
            if ($provider->type === 'SIP') {
465
                $account    = Sip::findFirst('disabled="0" AND uniqid = "' . $uniqueID . '"');
466
                $technology = ($account === null) ? '' : SIPConf::getTechnology();
467
            } elseif ($provider->type === 'IAX') {
468
                $account    = Iax::findFirst('disabled="0" AND uniqid = "' . $uniqueID . '"');
469
                $technology = ($account === null) ? '' : 'IAX2';
470
            }
471
        }
472
473
        return $technology;
474
    }
475
476
    /**
477
     * Генератор исходящего маршрута.
478
     *
479
     * @param $rout
480
     *
481
     * @return string
482
     */
483
    private function generateOutgoingRegexPattern($rout): string
484
    {
485
        $conf         = '';
486
        $regexPattern = '';
487
        
488
        $restNumbers = (int) ($rout['restnumbers']??0);
489
        if ($restNumbers > 0) {
490
            $regexPattern = "[0-9]{" . $rout['restnumbers'] . "}$";
491
        } elseif ($restNumbers === 0) {
492
            $regexPattern = "$";
493
        } elseif ($restNumbers === -1) {
494
            $regexPattern = "";
495
        }
496
        $numberBeginsWith = $rout['numberbeginswith']??'';
497
        $numberBeginsWith = str_replace(array('*', '+'), array('\\\\*', '\\\\+'), $numberBeginsWith);
498
        $conf            .= 'same => n,ExecIf($["${REGEX("^' . $numberBeginsWith . $regexPattern . '" ${EXTEN})}" == "1"]?Gosub(' . $rout['providerid'] . '-' . $rout['id'] . '-outgoing,${EXTEN},1))' . " \n\t";
499
500
        return $conf;
501
    }
502
503
    /**
504
     * Контекст для входящих внешних звонков без авторизации.
505
     *
506
     * @param $conf
507
     */
508
    public function generatePublicContext(&$conf): void
509
    {
510
        $additionalModules = $this->di->getShared(PBXConfModulesProvider::SERVICE_NAME);
511
        $conf              .= "\n";
512
        $conf              .= self::generateIncomingContextPeers('none');
513
        $conf              .= "[public-direct-dial] \n";
514
        foreach ($additionalModules as $appClass) {
515
            if ($appClass instanceof $this) {
516
                continue;
517
            }
518
            $appClass->generatePublicContext($conf);
519
        }
520
        $filter = ["provider IS NULL AND priority<>9999"];
521
522
        /**
523
         * @var array
524
         */
525
        $m_data = IncomingRoutingTable::find($filter);
526
        if (count($m_data->toArray()) > 0) {
527
            $conf .= 'include => none-incoming';
528
        }
529
    }
530
531
    /**
532
     * Генератор входящих контекстов.
533
     *
534
     * @param string | array $provider
535
     * @param string | array $login
536
     * @param string         $uniqid
537
     *
538
     * @return string
539
     */
540
    public static function generateIncomingContextPeers($provider, $login = '', $uniqid = ''): string
541
    {
542
        $conf     = '';
543
        $dialplan = [];
544
        $di       = Di::getDefault();
545
        if ($di === null) {
546
            return '';
547
        }
548
        $additionalModules = $di->getShared(PBXConfModulesProvider::SERVICE_NAME);
549
        $confExtensions    = ConferenceConf::getConferenceExtensions();
550
551
        if ('none' === $provider) {
552
            // Звонки по sip uri.
553
            $filter = [
554
                'provider IS NULL AND priority<>9999',
555
                'order' => 'provider,priority,extension',
556
            ];
557
        } elseif (is_array($provider)) {
558
            $filter = [
559
                'provider IN ({provider:array})',
560
                'bind'  => [
561
                    'provider' => array_keys($provider),
562
                ],
563
                'order' => 'provider,priority,extension',
564
            ];
565
        } else {
566
            // Звонки через провайдера.
567
            $filter = [
568
                "provider = '$provider'",
569
                'order' => 'provider,priority,extension',
570
            ];
571
        }
572
        /** @var IncomingRoutingTable $default_action */
573
        $default_action = IncomingRoutingTable::findFirst('priority = 9999');
574
        /** @var IncomingRoutingTable $m_data */
575
        $m_data = IncomingRoutingTable::find($filter);
576
        $data   = $m_data->toArray();
577
        uasort($data, __CLASS__ . '::sortArrayByPriority');
578
579
        $need_def_rout = true;
580
        foreach ($data as $rout) {
581
            $number = trim($rout['number']);
582
            if ($number === 'X!' || $number === '') {
583
                $need_def_rout = false;
584
                break;
585
            }
586
        }
587
        if ($need_def_rout === true && 'none' !== $provider) {
588
            $data[] = ['number' => '', 'extension' => '', 'timeout' => ''];
589
        }
590
        $config = new MikoPBXConfig();
591
        $lang   = str_replace('_', '-', $config->getGeneralSettings('PBXLanguage'));
592
593
        $rout_data_dial = [];
594
        foreach ($data as $rout) {
595
            $number      = trim($rout['number']);
596
            $timeout     = trim($rout['timeout']);
597
            $rout_number = ($number === '') ? 'X!' : $number;
598
            $rout_data   = &$dialplan[$rout_number];
599
            if (empty($rout_data)) {
600
                $ext_prefix = ('none' === $provider) ? '' : '_';
601
                $rout_data  .= "exten => {$ext_prefix}{$rout_number},1,NoOp(--- Incoming call ---)\n\t";
602
                $rout_data  .= 'same => n,Set(CHANNEL(language)=' . $lang . ')' . "\n\t";
603
                $rout_data  .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
604
                $rout_data  .= 'same => n,Set(__FROM_DID=${EXTEN})' . "\n\t";
605
                $rout_data  .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
606
607
                // Установка имени пира.
608
                $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
609
                $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
610
611
                $rout_data .= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)'."\n\t";
612
                // Проверим распискние для входящих внешних звонков.
613
                $rout_data .= 'same => n,Gosub(check-out-work-time,${EXTEN},1)'."\n\t";
614
                foreach ($additionalModules as $appClass) {
615
                    $addition = $appClass->generateIncomingRoutBeforeDial($rout_number);
616
                    if ( ! empty($addition)) {
617
                        $rout_data .= $appClass->confBlockWithComments($addition);
618
                    }
619
                }
620
                // Описываем возможность прыжка в пользовательский sub контекст.
621
                $rout_data .= " \n\t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)';
622
            }
623
624
            if ( ! empty($rout['extension'])) {
625
                $rout_data = rtrim($rout_data);
626
                // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
627
                // При ответе может отработать следующий приоритет.
628
                if ( ! isset($rout_data_dial[$rout_number])) {
629
                    $rout_data_dial[$rout_number] = '';
630
                }
631
632
                if (in_array($rout['extension'], $confExtensions, true)) {
633
                    // Это конференция. Тут не требуется обработка таймаута ответа.
634
                    // Вызов будет отвечен сразу конференцией.
635
                    $dial_command                 = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$rout['extension']},1));";
636
                    $rout_data_dial[$rout_number] .= "";
637
                } else {
638
                    $dial_command                 = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$rout['extension']}@internal-incoming/n,{$timeout},TKg));";
639
                    $rout_data_dial[$rout_number] .= " \n\t" . "same => n,Set(M_TIMEOUT={$timeout})";
640
                }
641
                $rout_data_dial[$rout_number] .= $dial_command;
642
643
                if (is_array($provider)) {
644
                    $key = $provider[$rout['provider']] ?? '';
645
                    if ( ! isset($rout_data_dial[$key])) {
646
                        $rout_data_dial[$key] = '';
647
                    }
648
                    if (empty($number)) {
649
                        $rout_data_dial[$key] .= $dial_command;
650
                    }
651
                }
652
            }
653
        }
654
655
        if (is_string($login)) {
656
            $add_login_pattern = ! empty($login);
657
            foreach ($data as $rout) {
658
                if ( ! $add_login_pattern) {
659
                    break;
660
                } // Логин не заполнен, обработка не требуется.
661
                $is_num = preg_match_all('/^\d+$/m', $login, $matches, PREG_SET_ORDER);
662
                if ($is_num === 1) {
663
                    // Это числовой номер, потому, не требуется дополнительно описывать exten.
664
                    $add_login_pattern = false;
665
                    break;
666
                }
667
                if (trim($rout['number']) !== $login) {
668
                    // Совпадение exten не найдено. Идем дальше.
669
                    continue;
670
                }
671
                // Совпадение найдено, не требуется дополнительно описывать exten.
672
                $add_login_pattern = false;
673
                break;
674
            }
675
            if ($add_login_pattern && array_key_exists('X!', $rout_data_dial) && isset($dialplan['X!'])) {
676
                $dialplan[$login]       = str_replace('_X!,1', "{$login},1", $dialplan['X!']);
677
                $rout_data_dial[$login] = $rout_data_dial['X!'];
678
            } elseif ($add_login_pattern === true && $need_def_rout === true && count($data) === 1) {
679
                // Только маршрут "По умолчанию".
680
                $dialplan[$login] = str_replace('_X!,1', "{$login},1", $dialplan['X!']);
681
            }
682
        } elseif (is_array($provider)) {
683
            foreach (array_values($provider) as $_login) {
684
                $dialplan[$_login] = str_replace('_X!,1', "{$_login},1", $dialplan['X!']);
685
            }
686
        }
687
688
        foreach ($dialplan as $key => &$dpln) {
689
            if ( ! array_key_exists($key, $rout_data_dial)) {
690
                continue;
691
            }
692
            $dpln = rtrim($dpln);
693
            $dpln .= $rout_data_dial[$key];
694
        }
695
        unset($dpln);
696
697
        $uniqid = is_string($provider) ? $provider : $uniqid;
698
        $conf   .= "\n" . "[{$uniqid}-incoming]\n";
699
        foreach ($dialplan as $dpln) {
700
            $conf .= $dpln . "\n";
701
            if (null === $default_action && 'none' !== $provider) {
702
                continue;
703
            }
704
            if ('extension' === $default_action->action) {
705
                // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
706
                // При ответе может отработать следующий приоритет.
707
                $conf .= "\t" . 'same => n,Set(M_TIMEOUT=0)' . "\n";
708
                if (in_array($default_action->extension, $confExtensions, true)) {
709
                    // Это конференция. Тут не требуется обработка таймаута ответа.
710
                    // Вызов будет отвечен сразу конференцией.
711
                    $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$default_action->extension},1)); default action" . "\n";
712
                } else {
713
                    $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$default_action->extension}@internal/n,,TKg)); default action" . "\n";
714
                }
715
                foreach ($additionalModules as $appClass) {
716
                    $addition = $appClass->generateIncomingRoutAfterDialContext($uniqid);
717
                    if ( ! empty($addition)) {
718
                        $conf .= $appClass->confBlockWithComments($addition);
719
                    }
720
                }
721
                $conf .= " \t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)' . "\n";
722
            } elseif ('busy' === $default_action->action) {
723
                $conf .= "\t" . "same => n,Busy()" . "\n";
724
            }
725
            $conf .= "\t" . "same => n,Hangup()" . "\n";
726
        }
727
728
        return $conf;
729
    }
730
731
    /**
732
     * Описываем нерабочее время.
733
     *
734
     * @param $conf
735
     *
736
     * @return string
737
     */
738
    private function generateOutWorkTimes(&$conf): string
739
    {
740
        $conf .= "\n\n[playback-exit]\n";
741
        $conf .= 'exten => _.!,1,NoOp(check time)' . "\n\t";
742
        $conf .= 'same => n,Gosub(dial_outworktimes,${EXTEN},1)' . "\n\t";
743
        $conf .= 'same => n,Playback(${filename})' . "\n\t";
744
        $conf .= 'same => n,Hangup()' . "\n\n";
745
746
        $checkContext = 'check-out-work-time';
747
        $conf .= "[".$checkContext."]\n";
748
        $conf .= "exten => _.!,1,NoOp(check time)\n\t";
749
        $conf .= 'same => n,Set(currentYear=${STRFTIME(,,%Y)})'."\n\t";
750
        $conf .= 'same => n,GosubIf($["${DIALPLAN_EXISTS('.$checkContext.'-${currentYear},${EXTEN},1)}" == "1"]?'.$checkContext.'-${currentYear},${EXTEN},1)'."\n\t";
751
752
        $data = OutWorkTimes::find(['order' => 'date_from']);
753
        $conf_out_set_var = '';
754
755
        $checkContextsYear = [];
756
        foreach ($data as $out_data) {
757
            $intervals = $this->getOutWorkIntervals($out_data->date_from,$out_data->date_to);
758
            foreach ($intervals as $interval){
759
                $ruleData = $out_data->toArray();
760
                $ruleData['date_to']    = $interval['date_to'];
761
                $ruleData['date_from']  = $interval['date_from'];
762
                $this->generateOutWorkRule($ruleData, $conf_out_set_var, $conf, $checkContextsYear);
763
            }
764
        }
765
        $conf .= "same => n,return\n\n";
766
        $conf .= $conf_out_set_var;
767
768
        foreach ($checkContextsYear as $year => $rule){
769
            $conf .= "[".$checkContext."-{$year}]\n";
770
            $conf .= "exten => _.!,1,NoOp(check time {$year} year)\n\t";
771
            $conf .= implode("", $rule);
772
            $conf .= "same => n,return\n";
773
        }
774
775
        return $conf;
776
    }
777
778
    private function getOutWorkIntervals($date_from, $date_to):array{
779
        $year_from  = 1*date('Y', (int)$date_from);
780
        $year_to    = 1*date('Y', (int)$date_to);
781
782
        $intervals = [];
783
        $Year = $year_from;
784
        if($Year === $year_to && $Year === $year_from){
785
            $intervals[] = [
786
                'date_from' => $date_from,
787
                'date_to'   => $date_to
788
            ];
789
            return $intervals;
790
        }
791
        while ($Year <= $year_to){
792
            if($Year === $year_from){
793
                $intervals[] = [
794
                    'date_from' => $date_from,
795
                    'date_to'   => (string)strtotime('31-12-'.$Year)
796
                ];
797
            }elseif ($Year === $year_to){
798
                $intervals[] = [
799
                    'date_from' => (string)strtotime('01-01-'.$Year),
800
                    'date_to'   => $date_to
801
                ];
802
            }else{
803
                $intervals[] = [
804
                    'date_from' => (string)strtotime('01-01-'.$Year),
805
                    'date_to' => (string)strtotime('31-12-'.$Year)
806
                ];
807
            }
808
            $Year++ ;
809
        }
810
        return $intervals;
811
    }
812
813
    /**
814
     * @param array  $out_data
815
     * @param string $conf_out_set_var
816
     * @param string $conf
817
     * @param array  $checkContextsYear
818
     */
819
    private function generateOutWorkRule(array $out_data, string & $conf_out_set_var, string & $conf, array & $checkContextsYear):void{
820
        $year_from = '';
821
        if ( !empty($out_data['date_from']) && !empty($out_data['date_to'])) {
822
            $year_from = date('Y', (int)$out_data['date_to']);
823
        }
824
825
        $time_from  = $out_data['time_from'];
826
        $time_to    = $out_data['time_to'];
827
        if (empty($time_from) && empty($time_to)) {
828
            $times = '*';
829
        } else {
830
            $time_to = (empty($time_to)) ? '23:59' : $time_to;
831
            $time_to = (strlen($time_to) === 4) ? "0{$time_to}" : $time_to;
832
833
            $time_from = (empty($time_from)) ? '00:00' : $time_from;
834
            $time_from = (strlen($time_from) === 4) ? "0{$time_from}" : $time_from;
835
836
            $times = "{$time_from}-{$time_to}";
837
        }
838
839
        $weekday_from = (string)$out_data['weekday_from'];
840
        $weekday_to = (string)$out_data['weekday_to'];
841
        $arr_weekday = [null, "mon", "tue", "wed", "thu", "fri", "sat", "sun"];
842
        if (empty($weekday_from) && empty($weekday_to)) {
843
            $weekdays = '*';
844
        } else {
845
            $weekday_from = (empty($weekday_from)) ? '1' : $weekday_from;
846
            $weekday_to = (empty($weekday_to)) ? '7' : $weekday_to;
847
            $weekdays = "{$arr_weekday[$weekday_from]}-{$arr_weekday[$weekday_to]}";
848
        }
849
850
        $date_from = $out_data['date_from'];
851
        $date_to   = $out_data['date_to'];
852
        if (empty($date_from)) {
853
            $mdays = '*';
854
            $months = '*';
855
        } else {
856
            $mdays = strtolower(date("j", (int)$date_from));
857
            $months = strtolower(date("M", (int)$date_from));
858
            if (!empty($date_to)) {
859
                $mdays .= "-" . strtolower(date("j", (int)$date_to));
860
                $months .= "-" . strtolower(date("M", (int)$date_to));
861
            }
862
        }
863
864
        if ('extension' === $out_data['action']) {
865
            $appname = 'GotoIfTime';
866
            $appdata = "internal,{$out_data['extension']},1";
867
        } else {
868
            /** @var SoundFiles $res */
869
            $res = SoundFiles::findFirst($out_data['audio_message_id']);
870
            $audio_message = ($res === null) ? '' : Util::trimExtensionForFile($res->path);
871
872
            $dialplanName = "work-time-set-var-{$out_data['id']}";
873
874
            if(strpos($conf_out_set_var, $dialplanName) === false){
875
                $conf_out_set_var .= "[{$dialplanName}]\n" . 'exten => _.!,1,Set(filename=' . $audio_message . ')' . "\n\t" . 'same => n,Goto(playback-exit,${EXTEN},1)' . "\n\n";
876
            }
877
878
            $appname = 'ExecIfTime';
879
            $appdata = 'Goto(work-time-set-var-' . $out_data['id'] . ',${EXTEN},1)';
880
        }
881
882
        $rule = "same => n,{$appname}($times,$weekdays,$mdays,$months?{$appdata})\n\t";
883
        if(empty($year_from)){
884
            $conf .= $rule;
885
        }else{
886
            $checkContextsYear[$year_from][] = $rule;
887
        }
888
    }
889
}