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