Passed
Push — develop ( 875b3a...f1c9cd )
by Портнов
12:57
created

initDaysMonthsInterval()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
dl 0
loc 19
rs 9.8666
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
/*
3
 * MikoPBX - free phone system for small business
4
 * Copyright © 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\Iax;
24
use MikoPBX\Common\Models\IncomingRoutingTable;
25
use MikoPBX\Common\Models\OutWorkTimes;
26
use MikoPBX\Common\Models\OutWorkTimesRouts;
27
use MikoPBX\Common\Models\Sip;
28
use MikoPBX\Common\Models\SoundFiles;
29
use MikoPBX\Core\System\Processes;
30
use MikoPBX\Core\System\Util;
31
32
/**
33
 * Class ExtensionsOutWorkTimeConf
34
 *
35
 * This class handles the generation of additional contexts sections in the extensions.conf file
36
 * for out of work time configurations.
37
 *
38
 * @package MikoPBX\Core\Asterisk\Configs
39
 */
40
class ExtensionsOutWorkTimeConf extends AsteriskConfigClass
41
{
42
    // The module hook applying priority
43
    public int $priority = 510;
44
    public const OUT_WORK_TIME_CONTEXT = 'check-out-work-time';
45
    private string $conf = '';
46
47
    /**
48
     * Generates core modules config files
49
     */
50
    protected function generateConfigProtected(): void
51
    {
52
        $config = '';
53
        /** @var OutWorkTimes $rule */
54
        $rules = OutWorkTimes::find();
55
        foreach ($rules as $rule){
56
            if(empty($rule->calType)){
57
                continue;
58
            }
59
            $config.= "[calendar-$rule->id]".PHP_EOL.
60
                'type='.$rule->calType.PHP_EOL.
61
                'url='.$rule->calUrl.PHP_EOL.
62
                'user='.$rule->calUser.PHP_EOL.
63
                'secret='.$rule->calSecret.PHP_EOL.
64
                'refresh=1'.PHP_EOL.
65
                'timeframe=60'.PHP_EOL.
66
                'channel = Local/s@calendar-event'.PHP_EOL.
67
                'app = Playback'.PHP_EOL.
68
                'appdata = beep'. PHP_EOL.PHP_EOL;
69
        }
70
        Util::fileWriteContent($this->config->path('asterisk.astetcdir') . '/calendar.conf', $config);
71
        $arr_out      = [];
72
        $pid = Processes::getPidOfProcess('asterisk');
73
        if(!empty($pid)){
74
            $asteriskPath = Util::which('asterisk');
75
            Processes::mwExec("{$asteriskPath} -rx 'module reload res_calendar'", $arr_out);
76
        }
77
78
    }
79
80
    /**
81
     * Generates the extension contexts for out of work time configurations.
82
     *
83
     * @return string The generated configuration.
84
     */
85
    public function extensionGenContexts(): string
86
    {
87
        $this->generateConfigProtected();
88
        $this->generateOutWorkTimes();
89
        return $this->conf;
90
    }
91
92
93
    /**
94
     *
95
     * @return string Set global vars.
96
     */
97
    public function extensionGlobals(): string
98
    {
99
        $configs = '';
100
        $dbData = Sip::find("type = 'friend' AND ( disabled <> '1')");
101
        foreach ($dbData as $sipPeer) {
102
            $context_id = SIPConf::getContextId($sipPeer->host, $sipPeer->port);
103
            $configs .= "CONTEXT_ID_$sipPeer->uniqid=$context_id".PHP_EOL;
104
        }
105
        return $configs;
106
    }
107
108
    /**
109
     * Generates the customized incoming context for a specific route before dialing system.
110
     *
111
     * @param string $rout_number The route number.
112
     *
113
     * @return string The generated configuration.
114
     */
115
    public function generateIncomingRoutBeforeDialSystem(string $rout_number): string
116
    {
117
        // Check the schedule for incoming external calls.
118
        return  'same => n,NoOp(contextID: ${contextID})' . PHP_EOL . "\t" .
119
                'same => n,ExecIf($["${CONTEXT}" == "public-direct-dial"]?Set(contextID=none-incoming))' . PHP_EOL . "\t" .
120
                'same => n,ExecIf($["${contextID}x" == "x"]?Set(contextID=${CONTEXT_ID_${providerID}}))' . PHP_EOL . "\t" .
121
                'same => n,ExecIf($["${contextID}x" == "x"]?Set(contextID=${CONTEXT}))' . PHP_EOL . "\t" .
122
                'same => n,GosubIf($["${IGNORE_TIME}" != "1"]?' . self::OUT_WORK_TIME_CONTEXT . ',${EXTEN},1)' . PHP_EOL . "\t";
123
    }
124
125
    /**
126
     * Generates the out of work time configurations.
127
     *
128
     * @return void
129
     */
130
    private function generateOutWorkTimes(): void
131
    {
132
        $this->conf =   PHP_EOL."[playback-exit]".PHP_EOL.
133
                        'exten => ' . ExtensionsConf::ALL_EXTENSION . ',1,Gosub(dial_outworktimes,${EXTEN},1)' . PHP_EOL."\t".
134
                        'same => n,Playback(${filename})' . PHP_EOL. "\t".
135
                        'same => n,Hangup()'.PHP_EOL.
136
                        'exten => _[hit],1,Hangup()'.PHP_EOL.PHP_EOL;
137
        $this->conf .=  '[calendar-event]'.PHP_EOL.
138
                        'exten  => s,1,NoOp( calendar: ${CALENDAR_EVENT(calendar)}, summary: ${CALENDAR_EVENT(summary)} )'.PHP_EOL."\t".
139
	                    'same => n,NoOp( description: ${CALENDAR_EVENT(description)} )'.PHP_EOL."\t".
140
	                    'same => n,NoOp( start: ${CALENDAR_EVENT(start)}, end: ${CALENDAR_EVENT(end)})'.PHP_EOL."\t".
141
	                    'same => n,NoOp( busystate: ${CALENDAR_EVENT(busystate)} )'.PHP_EOL."\t".
142
	                    'same => n,answer()'.PHP_EOL."\t".
143
	                    'same => n,Wait(2)' . PHP_EOL .
144
	                    'same => n,hangup' . PHP_EOL . PHP_EOL;
145
146
        $routesData = $this->getRoutesData();
147
        $additionalContexts = '';
148
        $conf_out_set_var = '';
149
        $data = OutWorkTimes::find(['order'=>'priority, date_from'])->toArray();
150
        $this->conf .= "[" . self::OUT_WORK_TIME_CONTEXT . "]".PHP_EOL;
151
        $this->conf .= 'exten => ' . ExtensionsConf::ALL_EXTENSION . ',1,Set(currentYear=${STRFTIME(,,%Y)})' . "\n\t";
152
        foreach ($data as $ruleData) {
153
            $contextId = 'check-out-work-time-'.$ruleData['id'];
154
            $this->conf .= 'same => n,Gosub('.$contextId.',${EXTEN},1)'.PHP_EOL."\t";
155
            [, $years] = $this->getOutWorkIntervals($ruleData['date_from'], $ruleData['date_to']);
156
            $additionalContexts.= '['.$contextId.']'.PHP_EOL;
157
            $additionalContexts.= 'exten => _[0-9*#+a-zA-Z]!,1,NoOp()'.PHP_EOL."\t";
158
            if(!empty($years)){
159
                $additionalContexts.= 'same => n,Set(TMP_YEARS='.implode('-',$years).'-)'.PHP_EOL."\t";
160
                $additionalContexts.= 'same => n,ExecIf($["STRREPLACE(TMP_YEARS,currentYear)" == "${TMP_YEARS}"]?return)'.PHP_EOL."\t";
161
            }
162
            // Restrictions for the route are not allowed for this rule.
163
            if ($ruleData['allowRestriction'] === '1') {
164
                $additionalContexts.= 'same => n,ExecIf($["${DIALPLAN_EXISTS('.$contextId.'-${contextID},${EXTEN},1)}" == "0"]?return)'.PHP_EOL."\t";
165
            }
166
            if(empty($ruleData['calType'])){
167
                $this->generateOutWorkRule($ruleData, $conf_out_set_var, $additionalContexts);
168
            }else{
169
                $appdata = $this->initRuleAppData($ruleData, $conf_out_set_var);
170
                $additionalContexts.= 'same => n,GotoIf(${CALENDAR_BUSY(calendar-'.$ruleData['id'].')}?'.$appdata.')'.PHP_EOL."\t";
171
            }
172
            $additionalContexts.= 'same => n,return'.PHP_EOL;
173
            $additionalContexts.= 'exten => _[hit],1,Hangup()'.PHP_EOL;
174
            $contextData = $routesData[$ruleData['id']]??[];
175
            foreach ($contextData as $subContext => $arrayDid){
176
                $additionalContexts.= "[$contextId-$subContext]".PHP_EOL;
177
                foreach (array_unique($arrayDid) as $did){
178
                    $additionalContexts .= "exten => $did,1,NoOp(-)" . PHP_EOL;
179
                }
180
            }
181
            $additionalContexts.= PHP_EOL;
182
183
        }
184
        $this->conf .= "same => n,return".PHP_EOL;
185
        $this->conf .= 'exten => _[hit],1,Hangup()' . PHP_EOL.PHP_EOL;
186
        $this->conf .= $additionalContexts.PHP_EOL;
187
        $this->conf .= PHP_EOL.$conf_out_set_var.PHP_EOL;
188
    }
189
190
    /**
191
     * Retrieves the data for the routes.
192
     *
193
     * @return array
194
     */
195
    private function getRoutesData(): array
196
    {
197
        $parameters = [
198
            'columns' => 'routId,timeConditionId',
199
        ];
200
        $allowedRouts    = OutWorkTimesRouts::find($parameters);
201
202
        $conditionsRoute = [];
203
        foreach ($allowedRouts as $tcRouteData){
204
            $conditionsRoute[$tcRouteData->routId][] = $tcRouteData->timeConditionId;
205
        }
206
        $filter = [
207
            'order' => 'priority',
208
            'conditions' => 'id>1'
209
        ];
210
        $rules = IncomingRoutingTable::find($filter);
211
        $routesData = [];
212
        foreach ($rules as $inRoute) {
213
            $provider = $inRoute->Providers;
214
            if ($provider) {
215
                $modelType = ucfirst($provider->type);
216
                $provByType = $provider->$modelType;
217
                if (get_class($provider->$modelType) === Iax::class) {
218
                    $context_id = "{$provider->uniqid}-incoming";
219
                }elseif ($provByType->registration_type === Sip::REG_TYPE_INBOUND){
220
                    $context_id = "{$provider->uniqid}-incoming";
221
                } else {
222
                    $context_id = SIPConf::getContextId($provByType->host , $provByType->port);
223
                }
224
            } else {
225
                $context_id = 'none-incoming';
226
            }
227
            foreach ($conditionsRoute[$inRoute->id]??[] as $timeConditionId){
228
                $routesData[$timeConditionId][$context_id][] = empty($inRoute->number) ? ExtensionsConf::ALL_EXTENSION : $inRoute->number;
229
            }
230
        }
231
        return $routesData;
232
    }
233
234
    /**
235
     * Get the out-of-work intervals based on the provided date range.
236
     *
237
     * @param int $date_from The starting date.
238
     * @param int $date_to The ending date.
239
     *
240
     * @return array An array of intervals.
241
     */
242
    private function getOutWorkIntervals($date_from, $date_to): array
243
    {
244
        $years = [];
245
        $year_from = 1 * date('Y', (int)$date_from);
246
        $year_to   = 1 * date('Y', (int)$date_to);
247
248
        $intervals = [];
249
        $Year = $year_from;
250
        if ($year_to === $year_from) {
251
            if(!empty($date_from)){
252
                $years[] = $Year;
253
            }
254
            $intervals[] = [
255
                'date_from' => $date_from,
256
                'date_to' => $date_to
257
            ];
258
            return [$intervals, $years];
259
        }
260
        while ($Year <= $year_to) {
261
262
            $years[] = $Year;
263
            if ($Year === $year_from) {
264
                $intervals[] = [
265
                    'date_from' => $date_from,
266
                    'date_to' => (string)strtotime('31-12-' . $Year)
267
                ];
268
            } elseif ($Year === $year_to) {
269
                $intervals[] = [
270
                    'date_from' => (string)strtotime('01-01-' . $Year),
271
                    'date_to' => $date_to
272
                ];
273
            } else {
274
                $intervals[] = [
275
                    'date_from' => (string)strtotime('01-01-' . $Year),
276
                    'date_to' => (string)strtotime('31-12-' . $Year)
277
                ];
278
            }
279
            $Year++;
280
        }
281
        return [$intervals, $years];
282
    }
283
284
    /**
285
     * Generate the out-of-work rule based on the provided data.
286
     *
287
     * @param array $out_data The data for the out-of-work rule.
288
     * @param string  &$conf_out_set_var The output string for the SET variables.
289
     * @param string  &$conf The output string for the configuration.
290
     *
291
     * @return void
292
     */
293
    private function generateOutWorkRule(array $out_data, string &$conf_out_set_var, string &$conf): void
294
    {
295
        $timesArray = $this->getTimesInterval($out_data);
296
        $weekdays   = $this->getWeekDayInterval($out_data);
297
298
        [$mDays, $months] = $this->initDaysMonthsInterval($out_data);
299
        $appdata = $this->initRuleAppData($out_data, $conf_out_set_var);
300
301
        foreach ($timesArray as $times) {
302
            $conf .= "same => n,GotoIfTime($times,$weekdays,$mDays,$months?{$appdata})\n\t";
303
        }
304
    }
305
306
    /**
307
     * Get the time intervals based on the provided out-of-work data.
308
     *
309
     * @param array $out_data The out-of-work data.
310
     *
311
     * @return array The time intervals.
312
     */
313
    private function getTimesInterval(array $out_data): array
314
    {
315
        $time_from  = $out_data['time_from'];
316
        $time_to    = $out_data['time_to'];
317
        if (empty($time_from) && empty($time_to)) {
318
            $intervals = ['*'];
319
        } else {
320
            $time_from  = $this->normaliseTime($time_from);
321
            $time_to    = $this->normaliseTime($time_to, '23:59');
322
            if (strtotime($time_from) > strtotime($time_to)) {
323
                $intervals = [
324
                    "{$time_from}-23:59",
325
                    "00:00-{$time_to}"
326
                ];
327
            } else {
328
                $intervals = [
329
                    "{$time_from}-{$time_to}"
330
                ];
331
            }
332
        }
333
        return $intervals;
334
    }
335
336
    /**
337
     * Normalize the time to an acceptable format.
338
     *
339
     * @param string $srcTime The source time to be normalized.
340
     * @param string $defVal The default value to be used if the source time is empty.
341
     * @return string The normalized time.
342
     */
343
    private function normaliseTime($srcTime, $defVal = '00:00'): string
344
    {
345
        $time = (empty($srcTime)) ? $defVal : $srcTime;
346
        return (strlen($time) === 4) ? "0{$time}" : $time;
347
    }
348
349
    /**
350
     * Initialize the rule application data based on the rule type.
351
     *
352
     * @param array $ruleData The rule data.
353
     * @param string $conf_out_set_var The reference to the configuration variable for output.
354
     *
355
     * @return string An array containing the data.
356
     */
357
    private function initRuleAppData($ruleData, string &$conf_out_set_var): string
358
    {
359
        if (IncomingRoutingTable::ACTION_EXTENSION === $ruleData['action']) {
360
            $appdata = "internal,{$ruleData['extension']},1";
361
        } else {
362
            /** @var SoundFiles $res */
363
            $res = SoundFiles::findFirst($ruleData['audio_message_id']);
364
            $audio_message = ($res === null) ? '' : Util::trimExtensionForFile($res->path??'');
365
            $dialplanName = "work-time-set-var-{$ruleData['id']}";
366
            if (strpos($conf_out_set_var, "[$dialplanName]") === false
367
                && strpos($this->conf, "[$dialplanName]") === false) {
368
                $conf_out_set_var .= "[{$dialplanName}]\n" .
369
                    'exten => ' . ExtensionsConf::ALL_EXTENSION . ',1,Set(filename=' . $audio_message . ')' . "\n\t" .
370
                    'same => n,Goto(playback-exit,${EXTEN},1)' . "\n" .
371
                    'exten => _[hit],1,Hangup()' . PHP_EOL . PHP_EOL;
372
            }
373
            $appdata = $dialplanName . ',${EXTEN},1';
374
        }
375
        return $appdata;
376
    }
377
378
    /**
379
     * Returns a string representing the weekday interval.
380
     *
381
     * @param array $out_data An array containing the weekday_from and weekday_to values.
382
     *
383
     * @return string The weekday interval string.
384
     */
385
    private function getWeekDayInterval(array $out_data): string
386
    {
387
        // Mapping of weekday numbers to abbreviations
388
        $arr_weekday = [null, "mon", "tue", "wed", "thu", "fri", "sat", "sun"];
389
390
        // Extract weekday_from and weekday_to values
391
        $weekday_from = (string)$out_data['weekday_from'];
392
        $weekday_to = (string)$out_data['weekday_to'];
393
394
        if (empty($weekday_from) && empty($weekday_to)) {
395
            $weekdays = '*';
396
        } else {
397
            $weekday_from = (empty($weekday_from)) ? '1' : $weekday_from;
398
            $weekday_to = (empty($weekday_to)) ? '7' : $weekday_to;
399
            $weekdays = "{$arr_weekday[$weekday_from]}-{$arr_weekday[$weekday_to]}";
400
        }
401
        return $weekdays;
402
    }
403
404
    /**
405
     * Initializes the days and months interval based on the provided data.
406
     *
407
     * @param array $out_data An array containing the date_from and date_to values.
408
     *
409
     * @return array An array containing the days and months interval.
410
     */
411
    private function initDaysMonthsInterval(array $out_data): array
412
    {
413
        // Extract date_from and date_to values
414
        $date_from = $out_data['date_from'];
415
        $date_to = $out_data['date_to'];
416
        if (empty($date_from)) {
417
            // If date_from is empty, set mDays and months to '*'
418
            $mDays = '*';
419
            $months = '*';
420
        } else {
421
            // Convert date_from to lowercase day and month abbreviations
422
            $mDays = strtolower(date("j", (int)$date_from));
423
            $months = strtolower(date("M", (int)$date_from));
424
            if (!empty($date_to)) {
425
                $mDays .= "-" . strtolower(date("j", (int)$date_to));
426
                $months .= "-" . strtolower(date("M", (int)$date_to));
427
            }
428
        }
429
        return array($mDays, $months);
430
    }
431
432
    public function generateModulesConf(): string
433
    {
434
        return "load => res_calendar.so".PHP_EOL.
435
               "load => res_calendar_caldav.so".PHP_EOL.
436
               "load => res_calendar_ews.so".PHP_EOL.
437
               "load => res_calendar_icalendar.so".PHP_EOL;
438
    }
439
}