Passed
Push — develop ( f628fc...4f13da )
by Портнов
04:39
created

IncomingContexts::trimDialplans()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 5
c 1
b 0
f 0
dl 0
loc 7
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\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
            self::generateRouteDialplan($rout_data, $provider, $rout, $lang, $additionalModules);
55
            self::generateDialActions($rout_data_dial, $rout, $rout_number, $confExtensions, $timeout, $provider, $number);
56
        }
57
58
        self::multiplyExtensionsInDialplan($dialplan, $rout_data_dial, $login, $data, $need_def_rout, $provider);
59
60
        self::trimDialplans($dialplan, $rout_data_dial);
61
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
        if (!empty($rout_data)) {
114
            return;
115
        }
116
        $number      = trim($rout['number']);
117
        $rout_number = ($number === '') ? 'X!' : $number;
118
119
        $ext_prefix = ('none' === $provider) ? '' : '_';
120
        $rout_data .= "exten => {$ext_prefix}{$rout_number},1,NoOp(--- Incoming call ---)\n\t";
121
        $rout_data .= 'same => n,Set(CHANNEL(language)=' . $lang . ')' . "\n\t";
122
        $rout_data .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
123
        $rout_data .= 'same => n,Set(__FROM_DID=${EXTEN})' . "\n\t";
124
        $rout_data .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
125
        // Установка имени пира.
126
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
127
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
128
        $rout_data .= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)' . "\n\t";
129
130
        foreach ($additionalModules as $appClass) {
131
            $addition = $appClass->generateIncomingRoutBeforeDial($rout_number);
132
            if (!empty($addition)) {
133
                $rout_data .= $appClass->confBlockWithComments($addition);
134
            }
135
        }
136
        // Описываем возможность прыжка в пользовательский sub контекст.
137
        $rout_data .= " \n\t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)';
138
139
        if (!empty($rout['extension'])) {
140
            $rout_data = rtrim($rout_data);
141
        }
142
    }
143
144
    /**
145
     * @param array  $rout_data_dial
146
     * @param        $rout
147
     * @param string $rout_number
148
     * @param array  $confExtensions
149
     * @param string $timeout
150
     * @param  $provider
151
     * @param string $number
152
     * @return void
153
     */
154
    private static function generateDialActions(array &$rout_data_dial, $rout, string $rout_number, array $confExtensions, string $timeout, $provider, string $number): void{
155
        if (empty($rout['extension'])) {
156
            return;
157
        }
158
        // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
159
        // При ответе может отработать следующий приоритет.
160
        if (!isset($rout_data_dial[$rout_number])) {
161
            $rout_data_dial[$rout_number] = '';
162
        }
163
164
        if (in_array($rout['extension'], $confExtensions, true)) {
165
            // Это конференция. Тут не требуется обработка таймаута ответа.
166
            // Вызов будет отвечен сразу конференцией.
167
            $dial_command = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$rout['extension']},1));";
168
            $rout_data_dial[$rout_number] .= "";
169
        } else {
170
            $dial_command = " \n\t" . 'same => n,' . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$rout['extension']}@internal-incoming/n,{$timeout},TKg));";
171
            $rout_data_dial[$rout_number] .= " \n\t" . "same => n,Set(M_TIMEOUT={$timeout})";
172
        }
173
        $rout_data_dial[$rout_number] .= $dial_command;
174
175
        if (!is_array($provider)) {
176
            return;
177
        }
178
179
        $key = $provider[$rout['provider']] ?? '';
180
        if (!isset($rout_data_dial[$key])) {
181
            $rout_data_dial[$key] = '';
182
        }
183
        if (empty($number)) {
184
            $rout_data_dial[$key] .= $dial_command;
185
        }
186
    }
187
188
    /**
189
     * @param array $dialplans
190
     * @param array $rout_data_dial
191
     */
192
    private static function trimDialplans(array $dialplans, array $rout_data_dial):void{
193
        foreach ($dialplans as $key => &$dialplan) {
194
            if (!array_key_exists($key, $rout_data_dial)) {
195
                continue;
196
            }
197
            $dialplan = rtrim($dialplan);
198
            $dialplan .= $rout_data_dial[$key];
199
        }
200
    }
201
202
203
    /**
204
     * Добавление дополнительных exten в Dialplan
205
     * @param array $dialplan
206
     * @param array $rout_data_dial
207
     * @param       $login
208
     * @param array $data
209
     * @param bool  $need_def_rout
210
     * @param $provider
211
     */
212
    private static function multiplyExtensionsInDialplan(array &$dialplan, array &$rout_data_dial, $login, array $data, bool $need_def_rout, $provider): void{
213
        if (is_string($login)) {
214
            $add_login_pattern = self::needAddLoginExtension($login, $data);
215
            if ($add_login_pattern && array_key_exists('X!', $rout_data_dial) && isset($dialplan['X!'])) {
216
                $dialplan[$login] = str_replace('_X!,1', "{$login},1", $dialplan['X!']);
217
                $rout_data_dial[$login] = $rout_data_dial['X!'];
218
            } elseif ($add_login_pattern === true && $need_def_rout === true && count($data) === 1) {
219
                // Только маршрут "По умолчанию".
220
                $dialplan[$login] = str_replace('_X!,1', "{$login},1", $dialplan['X!']);
221
            }
222
        } elseif (is_array($provider)) {
223
            foreach (array_values($provider) as $_login) {
224
                $dialplan[$_login] = str_replace('_X!,1', "{$_login},1", $dialplan['X!']);
225
            }
226
        }
227
    }
228
229
    /**
230
     * Если логин не числовой, то нужно добавить такой Exten.
231
     * @param string $login
232
     * @param array  $data
233
     * @return bool
234
     */
235
    private static function needAddLoginExtension(string $login, array $data): bool{
236
        $add_login_pattern = !empty($login);
237
        foreach ($data as $rout) {
238
            if (!$add_login_pattern) {
239
                break;
240
            } // Логин не заполнен, обработка не требуется.
241
            $is_num = preg_match_all('/^\d+$/m', $login, $matches, PREG_SET_ORDER);
242
            if ($is_num === 1) {
243
                // Это числовой номер, потому, не требуется дополнительно описывать exten.
244
                $add_login_pattern = false;
245
                break;
246
            }
247
            if (trim($rout['number']) !== $login) {
248
                // Совпадение exten не найдено. Идем дальше.
249
                continue;
250
            }
251
            // Совпадение найдено, не требуется дополнительно описывать exten.
252
            $add_login_pattern = false;
253
            break;
254
        }
255
        return $add_login_pattern;
256
    }
257
258
    /**
259
     * @param string               $conf
260
     * @param                      $provider
261
     * @param string               $uniqId
262
     * @param array                $dialplan
263
     * @param IncomingRoutingTable $default_action
264
     * @param array                $confExtensions
265
     * @param                      $additionalModules
266
     * @return string
267
     */
268
    private static function createSummaryDialplan(string & $conf, $provider, string $uniqId, array $dialplan, IncomingRoutingTable $default_action, array $confExtensions, $additionalModules): string{
269
        $uniqId = is_string($provider) ? $provider : $uniqId;
270
        $conf .= "\n" . "[{$uniqId}-incoming]\n";
271
        foreach ($dialplan as $dpln) {
272
            $conf .= $dpln . "\n";
273
            if (null === $default_action && 'none' !== $provider) {
274
                continue;
275
            }
276
            if ('extension' === $default_action->action) {
277
                // Обязательно проверяем "DIALSTATUS", в случае с парковой через AMI вызова это необходимо.
278
                // При ответе может отработать следующий приоритет.
279
                $conf .= "\t" . 'same => n,Set(M_TIMEOUT=0)' . "\n";
280
                if (in_array($default_action->extension, $confExtensions, true)) {
281
                    // Это конференция. Тут не требуется обработка таймаута ответа.
282
                    // Вызов будет отвечен сразу конференцией.
283
                    $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Goto(internal,{$default_action->extension},1)); default action" . "\n";
284
                } else {
285
                    $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?' . "Dial(Local/{$default_action->extension}@internal/n,,TKg)); default action" . "\n";
286
                }
287
                foreach ($additionalModules as $appClass) {
288
                    $addition = $appClass->generateIncomingRoutAfterDialContext($uniqId);
289
                    if (!empty($addition)) {
290
                        $conf .= $appClass->confBlockWithComments($addition);
291
                    }
292
                }
293
                $conf .= " \t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)' . "\n";
294
            } elseif ('busy' === $default_action->action) {
295
                $conf .= "\t" . "same => n,Busy()" . "\n";
296
            }
297
            $conf .= "\t" . "same => n,Hangup()" . "\n";
298
        }
299
        return $conf;
300
    }
301
302
}