Passed
Push — develop ( aa0db1...06f5e3 )
by Портнов
11:38
created

ExtensionsOutWorkTimeConf::getRoutesData()   A

Complexity

Conditions 5
Paths 10

Size

Total Lines 35
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 35
rs 9.1928
cc 5
nc 10
nop 0
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright (C) 2017-2021 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
21
namespace MikoPBX\Core\Asterisk\Configs;
22
23
use MikoPBX\Common\Models\IncomingRoutingTable;
24
use MikoPBX\Common\Models\OutWorkTimes;
25
use MikoPBX\Common\Models\OutWorkTimesRouts;
26
use MikoPBX\Common\Models\SoundFiles;
27
use MikoPBX\Core\System\Util;
28
29
class ExtensionsOutWorkTimeConf extends CoreConfigClass
30
{
31
    public const OUT_WORK_TIME_CONTEXT = 'check-out-work-time';
32
    public const DIGIT_ANY_EXTENSION = '_[0-9*#+a-zA-Z]!';
33
34
    /**
35
     * Генератор extensions, дополнительные контексты.
36
     * @return string
37
     */
38
    public function extensionGenContexts(): string
39
    {
40
        return $this->generateOutWorkTimes();
41
    }
42
43
    /**
44
     * Кастомизация входящего контекста для конкретного маршрута.
45
     *
46
     * @param string $rout_number
47
     *
48
     * @return string
49
     */
50
    public function generateIncomingRoutBeforeDialSystem(string $rout_number): string
51
    {
52
        // Проверим распискние для входящих внешних звонков.
53
        return  'same => n,ExecIf($["${contextID}x" == "x"]?Set(contextID=${CONTEXT}))'.PHP_EOL."\t".
54
                'same => n,GosubIf($["${IGNORE_TIME}" != "1"]?'.self::OUT_WORK_TIME_CONTEXT.',${EXTEN},1)'. PHP_EOL."\t";
55
    }
56
57
    /**
58
     * Описываем нерабочее время.
59
     *
60
     * @return string
61
     */
62
    private function generateOutWorkTimes(): string
63
    {
64
        $conf = "\n\n[playback-exit]\n";
65
        $conf .= 'exten => '.self::DIGIT_ANY_EXTENSION.',1,Gosub(dial_outworktimes,${EXTEN},1)' . "\n\t";
66
        $conf .= 'same => n,Playback(${filename})' . "\n\t";
67
        $conf .= 'same => n,Hangup()' . "\n";
68
        $conf .= 'exten => _[hit],1,NoOp()'.PHP_EOL.PHP_EOL;
69
70
        $conf .= $this->getWorkTimeDialplan(self::DIGIT_ANY_EXTENSION);
71
        return $conf;
72
    }
73
74
    private function getRoutesData(): array
75
    {
76
        $parameters      = [
77
            'columns'    => 'routId,timeConditionId',
78
        ];
79
        $allowedRouts    = OutWorkTimesRouts::find($parameters);
80
        $allowedRulesIds = array_column($allowedRouts->toArray(), 'routId');
81
        $filter = [
82
            'order' => 'priority',
83
            'conditions' => 'id IN ({ids:array})',
84
            'bind'       => [
85
                'ids' => $allowedRulesIds
86
            ]
87
        ];
88
        $rules        = IncomingRoutingTable::find($filter);
89
        $routesData   = [];
90
        foreach ($rules as $rule) {
91
            $provider = $rule->Providers;
92
            if ($provider) {
93
                $modelType  = ucfirst($provider->type);
94
                $provByType = $provider->$modelType;
95
                $context_id = SIPConf::getContextId($provByType->host.$provByType->port);
96
            } else {
97
                $context_id = 'none-incoming';
98
            }
99
            $routesData[$rule->id] = [
100
                'context' => $context_id,
101
                'did'     => empty($rule->number)?self::DIGIT_ANY_EXTENSION:$rule->number,
102
            ];
103
        }
104
        $tcData = [];
105
        foreach ($allowedRouts as $tcRoute){
106
            $tcData["".$tcRoute->timeConditionId][] = $routesData[$tcRoute->routId];
107
        }
108
        return $tcData;
109
    }
110
111
    /**
112
     * @param $extension
113
     * @param $checkContextsYear
114
     * @return string
115
     */
116
    private function getWorkTimeDialplan($extension):string
117
    {
118
        $routesData = $this->getRoutesData();
119
        $checkContextsYear = [];
120
        $conf_out_set_var  = '';
121
        $data = OutWorkTimes::find(['order' => 'date_from'])->toArray();
122
        $conf = "[".self::OUT_WORK_TIME_CONTEXT."]\n";
123
        $conf.= 'exten => '.$extension.',1,Set(currentYear=${STRFTIME(,,%Y)})'."\n\t";
124
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-${contextID},${EXTEN},1)}" == "1"]?${CONTEXT}-${contextID},${EXTEN},1)'."\n\t";
125
        $conf.= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-${currentYear},${EXTEN},1)}" == "1"]?${CONTEXT}-${currentYear},${EXTEN},1)'."\n\t";
126
        foreach ($data as $ruleData) {
127
            if($ruleData['allowRestriction'] !== '1'){
128
                // Для правила запрещены ограничения по маршрутам.
129
                unset($routesData[$ruleData['id']]);
130
            }
131
            if(isset($routesData[$ruleData['id']])){
132
                continue;
133
            }
134
            $intervals = $this->getOutWorkIntervals($ruleData['date_from'], $ruleData['date_to']);
135
            foreach ($intervals as $interval){
136
                $ruleData['date_to']    = $interval['date_to'];
137
                $ruleData['date_from']  = $interval['date_from'];
138
                $this->generateOutWorkRule($ruleData, $conf_out_set_var, $conf, $checkContextsYear);
139
            }
140
        }
141
        $conf .= "same => n,return\n\n";
142
        $conf .= 'exten => _[hit],1,NoOp()'.PHP_EOL;
143
        $conf .= $conf_out_set_var;
144
145
        foreach ($checkContextsYear as $year => $rule){
146
            $conf .= "[".self::OUT_WORK_TIME_CONTEXT."-{$year}]\n";
147
            $conf .= 'exten => '.self::DIGIT_ANY_EXTENSION.",1,NoOp(check time {$year} year)\n\t";
148
            $conf .= implode("", $rule);
149
            $conf .= "same => n,return\n";
150
            $conf .= 'exten => _[hit],1,NoOp()'.PHP_EOL;
151
        }
152
153
154
        $confByContext = [];
155
        foreach ($routesData as $routData){
156
            foreach ($routData as $didData) {
157
                $confByContext[$didData['context']][$didData['did']] = '';
158
            }
159
        }
160
161
        $checkContextsYear = [];
162
        foreach ($confByContext as $contextKey => &$confContext){
163
            $conf_out_set_var  = '';
164
            $conf .= "[".self::OUT_WORK_TIME_CONTEXT."-$contextKey]\n";
165
            $dialplanDid = [];
166
            foreach ($confContext as $did => $confDid){
167
                if(!isset($dialplanDid[$did])){
168
                    $dialplanDid[$did] = '';
169
                }
170
                foreach ($data as $ruleData) {
171
                    $didArray =  array_column($routesData[$ruleData['id']], 'did');
172
                    if(!isset($routesData[$ruleData['id']]) || ! in_array((string)$did, $didArray, true)){
173
                        continue;
174
                    }
175
                    $intervals = $this->getOutWorkIntervals($ruleData['date_from'], $ruleData['date_to']);
176
                    foreach ($intervals as $interval){
177
                        $ruleData['date_to']    = $interval['date_to'];
178
                        $ruleData['date_from']  = $interval['date_from'];
179
                        $checkContextsYearDid = [];
180
                        $this->generateOutWorkRule($ruleData, $conf_out_set_var, $dialplanDid[$did], $checkContextsYearDid);
181
                        $checkContextsYear[$did][] = $checkContextsYearDid;
182
                    }
183
                }
184
185
                $tmpConf = [];
186
                foreach ($data as $ruleData) {
187
                    $didArray =  array_column($routesData[$ruleData['id']], 'did');
188
                    if(!isset($routesData[$ruleData['id']]) || ! in_array((string)$did, $didArray, true)){
189
                        continue;
190
                    }
191
                    $tmpConf[$did]= 'exten => '.ExtensionsConf::getExtenByDid($did).',1,NoOp()'."\n\t";
192
                    $tmpConf[$did].= 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-${currentYear},${EXTEN},1)}" == "1"]?${CONTEXT}-${currentYear},${EXTEN},1)'."\n\t";
193
                    $tmpConf[$did].= $dialplanDid[$did];
194
                    $tmpConf[$did].= "same => n,return\n\n";
195
                }
196
                $conf.= implode('', $tmpConf);
197
            }
198
            $conf .= 'exten => _[hit],1,NoOp()'.PHP_EOL.PHP_EOL;
199
            $confYear = [];
200
            foreach ($checkContextsYear as $did => $tmpArr){
201
                foreach ($tmpArr as $years){
202
                    foreach ($years as $year => $rule){
203
                        $confYear[$year] .= 'exten => '.ExtensionsConf::getExtenByDid($did).",1,NoOp(check time {$year} year)\n\t";
204
                        $confYear[$year] .= implode("", $rule);
205
                        $confYear[$year] .= "same => n,return\n";
206
                    }
207
                }
208
            }
209
            foreach ($confYear as $year => $rule){
210
                $conf .= "[".self::OUT_WORK_TIME_CONTEXT."-$contextKey-{$year}]\n";
211
                $conf .= $rule;
212
                $conf .= 'exten => _[hit],1,NoOp()'.PHP_EOL.PHP_EOL;
213
            }
214
            $conf .= $conf_out_set_var;
215
        }
216
        return $conf;
217
    }
218
219
    /**
220
     * Получает массив интервалов для разных "Годов"
221
     * @param $date_from
222
     * @param $date_to
223
     * @return array
224
     */
225
    private function getOutWorkIntervals($date_from, $date_to):array{
226
        $year_from  = 1*date('Y', (int)$date_from);
227
        $year_to    = 1*date('Y', (int)$date_to);
228
229
        $intervals = [];
230
        $Year = $year_from;
231
        if($year_to === $year_from){
232
            $intervals[] = [
233
                'date_from' => $date_from,
234
                'date_to'   => $date_to
235
            ];
236
            return $intervals;
237
        }
238
        while ($Year <= $year_to){
239
            if($Year === $year_from){
240
                $intervals[] = [
241
                    'date_from' => $date_from,
242
                    'date_to'   => (string)strtotime('31-12-'.$Year)
243
                ];
244
            }elseif ($Year === $year_to){
245
                $intervals[] = [
246
                    'date_from' => (string)strtotime('01-01-'.$Year),
247
                    'date_to'   => $date_to
248
                ];
249
            }else{
250
                $intervals[] = [
251
                    'date_from' => (string)strtotime('01-01-'.$Year),
252
                    'date_to'   => (string)strtotime('31-12-'.$Year)
253
                ];
254
            }
255
            $Year++ ;
256
        }
257
        return $intervals;
258
    }
259
260
    /**
261
     * Формирование правила переключателя по времени.
262
     * @param array  $out_data
263
     * @param string $conf_out_set_var
264
     * @param string $conf
265
     * @param array  $checkContextsYear
266
     */
267
    private function generateOutWorkRule(array $out_data, string & $conf_out_set_var, string & $conf, array & $checkContextsYear):void{
268
        $year_from = '';
269
        if ( !empty($out_data['date_from']) && !empty($out_data['date_to'])) {
270
            $year_from = date('Y', (int)$out_data['date_to']);
271
        }
272
273
        $timesArray = $this->getTimesInterval($out_data);
274
        $weekdays   = $this->getWeekDayInterval($out_data);
275
276
        [$mDays,    $months]  = $this->initDaysMonthsInterval($out_data);
277
        [$appName,  $appdata] = $this->initRuleAppData($out_data, $conf_out_set_var);
278
279
        foreach ($timesArray as $times){
280
            $rule = "same => n,{$appName}($times,$weekdays,$mDays,$months?{$appdata})\n\t";
281
            if(empty($year_from)){
282
                $conf .= $rule;
283
            }else{
284
                $checkContextsYear[$year_from][] = $rule;
285
            }
286
        }
287
    }
288
289
    /**
290
     * Получает интервалы времени.
291
     * @param array $out_data
292
     * @return array
293
     */
294
    private function getTimesInterval(array $out_data): array{
295
        $time_from  = $out_data['time_from'];
296
        $time_to    = $out_data['time_to'];
297
        if (empty($time_from) && empty($time_to)) {
298
            $intervals = ['*'];
299
        } else {
300
            $time_from  = $this->normaliseTime($time_from);
301
            $time_to    = $this->normaliseTime($time_to, '23:59');
302
            if(strtotime($time_from) > strtotime($time_to)){
303
                $intervals=[
304
                    "{$time_from}-23:59",
305
                    "00:00-{$time_to}"
306
                ];
307
            }else{
308
                $intervals=[
309
                    "{$time_from}-{$time_to}"
310
                ];
311
            }
312
        }
313
        return $intervals;
314
    }
315
316
    /**
317
     * Нормализация времени в приемлемый формат.
318
     * @param        $srcTime
319
     * @param string $defVal
320
     * @return string
321
     */
322
    private function normaliseTime($srcTime, $defVal = '00:00'):string{
323
        $time = (empty($srcTime)) ? $defVal : $srcTime;
324
        return (strlen($time) === 4) ? "0{$time}" : $time;
325
    }
326
327
    /**
328
     * Устанавливает тип и данные приложения.
329
     * @param        $ruleData
330
     * @param string $conf_out_set_var
331
     * @return string[]
332
     */
333
    private function initRuleAppData($ruleData, string &$conf_out_set_var): array{
334
        if ('extension' === $ruleData['action']) {
335
            $appName = 'GotoIfTime';
336
            $appdata = "internal,{$ruleData['extension']},1";
337
        } else {
338
            /** @var SoundFiles $res */
339
            $res = SoundFiles::findFirst($ruleData['audio_message_id']);
340
            $audio_message = ($res === null) ? '' : Util::trimExtensionForFile($res->path);
341
342
            $dialplanName = "work-time-set-var-{$ruleData['id']}";
343
344
            if (strpos($conf_out_set_var, $dialplanName) === false) {
345
                $conf_out_set_var .= "[{$dialplanName}]\n" .
346
                    'exten => '.self::DIGIT_ANY_EXTENSION.',1,Set(filename=' . $audio_message . ')'."\n\t" .
347
                        'same => n,Goto(playback-exit,${EXTEN},1)'."\n".
348
                    'exten => _[hit],1,NoOp()'.PHP_EOL.PHP_EOL;
349
            }
350
            $appName = 'ExecIfTime';
351
            $appdata = 'Goto(' . $dialplanName . ',${EXTEN},1)';
352
        }
353
        return array($appName, $appdata);
354
    }
355
356
    /**
357
     * Возвращает диапазон дней недели.
358
     * @param array $out_data
359
     * @return string
360
     */
361
    private function getWeekDayInterval(array $out_data): string{
362
        $weekday_from = (string)$out_data['weekday_from'];
363
        $weekday_to = (string)$out_data['weekday_to'];
364
        $arr_weekday = [null, "mon", "tue", "wed", "thu", "fri", "sat", "sun"];
365
        if (empty($weekday_from) && empty($weekday_to)) {
366
            $weekdays = '*';
367
        } else {
368
            $weekday_from = (empty($weekday_from)) ? '1' : $weekday_from;
369
            $weekday_to = (empty($weekday_to)) ? '7' : $weekday_to;
370
            $weekdays = "{$arr_weekday[$weekday_from]}-{$arr_weekday[$weekday_to]}";
371
        }
372
        return $weekdays;
373
    }
374
375
    /**
376
     * Возвращает диапазон месяцев.
377
     * @param array $out_data
378
     * @return string[]
379
     */
380
    private function initDaysMonthsInterval(array $out_data): array{
381
        $date_from = $out_data['date_from'];
382
        $date_to = $out_data['date_to'];
383
        if (empty($date_from)) {
384
            $mDays = '*';
385
            $months = '*';
386
        } else {
387
            $mDays = strtolower(date("j", (int)$date_from));
388
            $months = strtolower(date("M", (int)$date_from));
389
            if (!empty($date_to)) {
390
                $mDays .= "-" . strtolower(date("j", (int)$date_to));
391
                $months .= "-" . strtolower(date("M", (int)$date_to));
392
            }
393
        }
394
        return array($mDays, $months);
395
    }
396
397
398
}