Passed
Push — develop ( 4f13da...18615a )
by Портнов
04:11
created

IncomingContexts::createSummaryDialplanGoto()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Importance

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