Passed
Push — develop ( 447c71...9a0be0 )
by Портнов
04:16 queued 10s
created

IncomingContexts::createSummaryDialplanDefAction()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

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