Passed
Push — develop ( 06fb87...d66898 )
by Nikolay
23:43
created

IncomingContexts   F

Complexity

Total Complexity 60

Size/Duplication

Total Lines 372
Duplicated Lines 0 %

Importance

Changes 5
Bugs 0 Features 0
Metric Value
wmc 60
eloc 146
dl 0
loc 372
rs 3.6
c 5
b 0
f 0

19 Methods

Rating   Name   Duplication   Size   Complexity  
A generateDialActionsRoutNumber() 0 18 3
A needDefRout() 0 12 4
A needAddLoginExtension() 0 23 5
A generateRouteDialplan() 0 26 5
A defaultRouteOnly() 0 3 3
A checkNeedDefRout() 0 8 3
A getSettings() 0 6 1
A createSummaryDialplanDefAction() 0 12 3
A isMultipleRoutes() 0 3 3
A trimDialPlan() 0 8 3
A multiplyExtensionsInDialplanStringLogin() 0 9 3
A getRoutes() 0 21 3
A makeDialplan() 0 10 2
A createSummaryDialplanGoto() 0 18 2
A createSummaryDialplan() 0 16 5
A generate() 0 9 1
A multiplyExtensionsInDialplan() 0 8 4
A generateDialActions() 0 9 3
A duplicateDialActionsRoutNumber() 0 11 4

How to fix   Complexity   

Complex Class

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

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

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

1
<?php
2
3
4
namespace MikoPBX\Core\Asterisk\Configs\Generators\Extensions;
5
6
7
use MikoPBX\Common\Models\IncomingRoutingTable;
8
use MikoPBX\Core\Asterisk\Configs\ConferenceConf;
9
use MikoPBX\Core\Asterisk\Configs\CoreConfigClass;
10
use MikoPBX\Core\Asterisk\Configs\ExtensionsConf;
11
12
class IncomingContexts extends CoreConfigClass
13
{
14
    public $provider;
15
    public $login;
16
    public string $uniqId;
17
18
    private array $dialplan = [];
19
    private array $rout_data_dial = [];
20
    private array $confExtensions = [];
21
    private array $routes = [];
22
    private string $lang;
23
    private bool $need_def_rout;
24
25
    /**
26
     * Генератор входящих контекстов.
27
     *
28
     * @param string | array $provider
29
     * @param string | array $login
30
     * @param string         $uniqId
31
     *
32
     * @return string
33
     */
34
    public static function generate($provider, $login = '', $uniqId = ''): string
35
    {
36
        $generator           = new self();
37
        $generator->provider = $provider;
38
        $generator->login    = $login;
39
        $generator->uniqId   = $uniqId;
40
        $generator->getSettings();
41
42
        return $generator->makeDialplan();
43
    }
44
45
    public function getSettings(): void
46
    {
47
        $this->confExtensions = ConferenceConf::getConferenceExtensions();
48
        $this->routes         = $this->getRoutes();
49
        $this->lang           = str_replace('_', '-', $this->generalSettings['PBXLanguage']);
50
        $this->need_def_rout  = $this->checkNeedDefRout();
51
    }
52
53
    /**
54
     * Возвращает массив входящих маршрутов для провайдера.
55
     *
56
     * @return array|string[]
57
     */
58
    private function getRoutes(): array
59
    {
60
        if ('none' === $this->provider) {
61
            // Звонки по sip uri.
62
            $filter = ['provider IS NULL AND priority<>9999', 'order' => 'provider,priority,extension',];
63
        } elseif (is_array($this->provider)) {
64
            $filter = [
65
                'provider IN ({provider:array})',
66
                'bind'  => ['provider' => array_keys($this->provider),],
67
                'order' => 'provider,priority,extension',
68
            ];
69
        } else {
70
            // Звонки через провайдера.
71
            $filter = ["provider = '$this->provider'", 'order' => 'provider,priority,extension',];
72
        }
73
74
        $m_data = IncomingRoutingTable::find($filter);
75
        $data   = $m_data->toArray();
76
        uasort($data, ExtensionsConf::class . '::sortArrayByPriority');
77
78
        return $data;
79
    }
80
81
    /**
82
     * Проверка нужен ли дефолтный маршрут для провайдера.
83
     * Наполнение таблицы маршрутизаци значением по умолчанию.
84
     *
85
     * @return bool
86
     */
87
    private function checkNeedDefRout(): bool
88
    {
89
        $need_def_rout = $this->needDefRout();
90
        if ($need_def_rout === true && 'none' !== $this->provider) {
91
            $this->routes[] = ['number' => '', 'extension' => '', 'timeout' => ''];
92
        }
93
94
        return $need_def_rout;
95
    }
96
97
    /**
98
     * Проверка нужен ли дефолтный маршрут для провайдера.
99
     *
100
     * @return bool
101
     */
102
    private function needDefRout(): bool
103
    {
104
        $need_def_rout = true;
105
        foreach ($this->routes as $rout) {
106
            $number = trim($rout['number']);
107
            if ($number === 'X!' || $number === '') {
108
                $need_def_rout = false;
109
                break;
110
            }
111
        }
112
113
        return $need_def_rout;
114
    }
115
116
    public function makeDialplan(): string
117
    {
118
        foreach ($this->routes as $rout) {
119
            $this->generateRouteDialplan($rout);
120
            $this->generateDialActions($rout);
121
        }
122
        $this->multiplyExtensionsInDialplan();
123
        $this->trimDialPlan();
124
125
        return $this->createSummaryDialplan();
126
    }
127
128
    /**
129
     * Генерация Первой части (заголовка) входящего контекста.
130
     *
131
     * @param array $rout
132
     */
133
    private function generateRouteDialplan(array $rout): void
134
    {
135
        $number      = trim($rout['number']);
136
        $rout_number = ($number === '') ? 'X!' : $number;
137
        $rout_data   = &$this->dialplan[$rout_number];
138
        if ( ! empty($rout_data)) {
139
            return;
140
        }
141
        $ext_prefix = ('none' === $this->provider) ? '' : '_';
142
143
        $rout_data .= "exten => {$ext_prefix}{$rout_number},1,NoOp(--- Incoming call ---)\n\t";
144
        $rout_data .= 'same => n,Set(CHANNEL(language)=' . $this->lang . ')' . "\n\t";
145
        $rout_data .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
146
        $rout_data .= 'same => n,Set(__FROM_DID=${EXTEN})' . "\n\t";
147
        $rout_data .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
148
        // Установка имени пира.
149
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
150
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
151
        $rout_data .= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)' . "\n\t";
152
153
        $rout_data .= $this->hookModulesMethod(CoreConfigClass::GENERATE_INCOMING_ROUT_BEFORE_DIAL, [$rout_number]);
154
        // Описываем возможность прыжка в пользовательский sub контекст.
155
        $rout_data .= " \n\t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)';
156
157
        if ( ! empty($rout['extension'])) {
158
            $rout_data = rtrim($rout_data);
159
        }
160
    }
161
162
    /**
163
     * Формирует в Dialplan команды-действия goto dial.
164
     *
165
     * @param        $rout
166
     *
167
     * @return void
168
     */
169
    private function generateDialActions($rout): void
170
    {
171
        if (empty($rout['extension'])) {
172
            return;
173
        }
174
        $number      = trim($rout['number']);
175
        $rout_number = ($number === '') ? 'X!' : $number;
176
        $this->generateDialActionsRoutNumber($rout, $rout_number);
177
        $this->duplicateDialActionsRoutNumber($rout, $rout_number, $number);
178
    }
179
180
    private function generateDialActionsRoutNumber($rout, $rout_number): void
181
    {
182
        $timeout = trim($rout['timeout']);
183
        // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
184
        // При ответе может отработать следующий приоритет.
185
        if ( ! isset($this->rout_data_dial[$rout_number])) {
186
            $this->rout_data_dial[$rout_number] = '';
187
        }
188
        if (in_array($rout['extension'], $this->confExtensions, true)) {
189
            // Это конференция. Тут не требуется обработка таймаута ответа.
190
            // Вызов будет отвечен сразу конференцией.
191
            $dial_command                       = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$rout['extension']},1));";
192
            $this->rout_data_dial[$rout_number] .= "";
193
        } else {
194
            $dial_command                       = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$rout['extension']}@internal-incoming/n,{$timeout},TKg));";
195
            $this->rout_data_dial[$rout_number] .= " \n\t" . "same => n,Set(M_TIMEOUT={$timeout})";
196
        }
197
        $this->rout_data_dial[$rout_number] .= $dial_command;
198
    }
199
200
    private function duplicateDialActionsRoutNumber($rout, $rout_number, $number): void
201
    {
202
        if ( ! is_array($this->provider)) {
203
            return;
204
        }
205
        $key = $this->provider[$rout['provider']] ?? '';
206
        if ( ! isset($this->rout_data_dial[$key])) {
207
            $this->rout_data_dial[$key] = '';
208
        }
209
        if (empty($number)) {
210
            $this->rout_data_dial[$key] .= $this->rout_data_dial[$rout_number];
211
        }
212
    }
213
214
    /**
215
     * Добавление дополнительных exten в Dialplan
216
     */
217
    private function multiplyExtensionsInDialplan(): void
218
    {
219
        if (is_string($this->login)) {
220
            $this->multiplyExtensionsInDialplanStringLogin();
221
        }
222
        if (is_array($this->provider)) {
223
            foreach (array_values($this->provider) as $_login) {
224
                $this->dialplan[$_login] = str_replace('_X!,1', "{$_login},1", $this->dialplan['X!']);
225
            }
226
        }
227
    }
228
229
    /**
230
     * Добавляет extensions для dialplan.
231
     */
232
    private function multiplyExtensionsInDialplanStringLogin(): void
233
    {
234
        $add_login_pattern = $this->needAddLoginExtension();
235
        if ($this->isMultipleRoutes($add_login_pattern)) {
236
            $this->dialplan[$this->login]       = str_replace('_X!,1', "{$this->login},1", $this->dialplan['X!']);
237
            $this->rout_data_dial[$this->login] = $this->rout_data_dial['X!'];
238
        } elseif ($this->defaultRouteOnly($add_login_pattern)) {
239
            // Только маршрут "По умолчанию".
240
            $this->dialplan[$this->login] = str_replace('_X!,1', "{$this->login},1", $this->dialplan['X!']);
241
        }
242
    }
243
244
    /**
245
     * Если логин не числовой, то нужно добавить такой Exten.
246
     *
247
     * @return bool
248
     */
249
    private function needAddLoginExtension(): bool
250
    {
251
        $add_login_pattern = ! empty($this->login);
252
        foreach ($this->routes as $rout) {
253
            if ( ! $add_login_pattern) {
254
                break;
255
            } // Логин не заполнен, обработка не требуется.
256
            $is_num = preg_match_all('/^\d+$/m', $this->login, $matches, PREG_SET_ORDER);
257
            if ($is_num === 1) {
258
                // Это числовой номер, потому, не требуется дополнительно описывать exten.
259
                $add_login_pattern = false;
260
                break;
261
            }
262
            if (trim($rout['number']) !== $this->login) {
263
                // Совпадение exten не найдено. Идем дальше.
264
                continue;
265
            }
266
            // Совпадение найдено, не требуется дополнительно описывать exten.
267
            $add_login_pattern = false;
268
            break;
269
        }
270
271
        return $add_login_pattern;
272
    }
273
274
    /**
275
     * Несколько возможных входящих маршрутов "Каскад".
276
     *
277
     * @param bool $add_login_pattern
278
     *
279
     * @return bool
280
     */
281
    private function isMultipleRoutes(bool $add_login_pattern): bool
282
    {
283
        return $add_login_pattern && array_key_exists('X!', $this->rout_data_dial) && isset($this->dialplan['X!']);
284
    }
285
286
    /**
287
     * Проверка. Нужен только маршрут по умолчанию.
288
     *
289
     * @param bool $add_login_pattern
290
     *
291
     * @return bool
292
     */
293
    private function defaultRouteOnly(bool $add_login_pattern): bool
294
    {
295
        return $add_login_pattern === true && $this->need_def_rout === true && count($this->routes) === 1;
296
    }
297
298
    /**
299
     * Отрезает пробелы справа для dialplan.
300
     */
301
    private function trimDialPlan(): void
302
    {
303
        foreach ($this->dialplan as $key => &$dialplan) {
304
            if ( ! array_key_exists($key, $this->rout_data_dial)) {
305
                continue;
306
            }
307
            $dialplan = rtrim($dialplan);
308
            $dialplan .= $this->rout_data_dial[$key];
309
        }
310
    }
311
312
    /**
313
     * Формирование итогового dialplan.
314
     *
315
     * @return string
316
     */
317
    private function createSummaryDialplan(): string
318
    {
319
        /** @var IncomingRoutingTable $default_action */
320
        $default_action = IncomingRoutingTable::findFirst('priority = 9999');
321
322
        $uniqId = is_string($this->provider) ? $this->provider : $this->uniqId;
323
        $conf   = "\n" . "[{$uniqId}-incoming]\n";
324
        foreach ($this->dialplan as $dpln) {
325
            $conf .= $dpln . "\n";
326
            if (null === $default_action && 'none' !== $this->provider) {
327
                continue;
328
            }
329
            $conf .= $this->createSummaryDialplanDefAction($default_action, $uniqId);
330
        }
331
332
        return $conf;
333
    }
334
335
    /**
336
     * Формирование действия по умолчанию в dialplan.
337
     *
338
     * @param IncomingRoutingTable $default_action
339
     * @param string               $uniqId
340
     *
341
     * @return string
342
     */
343
    private function createSummaryDialplanDefAction(IncomingRoutingTable $default_action, string $uniqId): string
344
    {
345
        $conf = '';
346
        if ('extension' === $default_action->action) {
347
            $conf = $this->createSummaryDialplanGoto($conf, $default_action, $uniqId);
348
            $conf .= " \t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)' . "\n";
349
        } elseif ('busy' === $default_action->action) {
350
            $conf .= "\t" . "same => n,Busy()" . "\n";
351
        }
352
        $conf .= "\t" . "same => n,Hangup()" . "\n";
353
354
        return $conf;
355
    }
356
357
    /**
358
     * Формирование Goto действия для итогового dialplan.
359
     *
360
     * @param string               $conf
361
     * @param IncomingRoutingTable $default_action
362
     * @param string               $uniqId
363
     *
364
     * @return string
365
     */
366
    private function createSummaryDialplanGoto(
367
        string $conf,
368
        IncomingRoutingTable $default_action,
369
        string $uniqId
370
    ): string {
371
        // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
372
        // При ответе может отработать следующий приоритет.
373
        $conf .= "\t" . 'same => n,Set(M_TIMEOUT=0)' . "\n";
374
        if (in_array($default_action->extension, $this->confExtensions, true)) {
375
            // Это конференция. Тут не требуется обработка таймаута ответа.
376
            // Вызов будет отвечен сразу конференцией.
377
            $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$default_action->extension},1)); default action" . "\n";
378
        } else {
379
            $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$default_action->extension}@internal/n,,TKg)); default action" . "\n";
380
        }
381
        $conf .= $this->hookModulesMethod(CoreConfigClass::GENERATE_INCOMING_ROUT_AFTER_DIAL_CONTEXT, [$uniqId]);
382
383
        return $conf;
384
    }
385
386
}