IncomingContexts::isMultipleRoutes()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 3
nc 3
nop 1
1
<?php
2
3
/*
4
 * MikoPBX - free phone system for small business
5
 * Copyright © 2017-2023 Alexey Portnov and Nikolay Beketov
6
 *
7
 * This program is free software: you can redistribute it and/or modify
8
 * it under the terms of the GNU General Public License as published by
9
 * the Free Software Foundation; either version 3 of the License, or
10
 * (at your option) any later version.
11
 *
12
 * This program is distributed in the hope that it will be useful,
13
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
 * GNU General Public License for more details.
16
 *
17
 * You should have received a copy of the GNU General Public License along with this program.
18
 * If not, see <https://www.gnu.org/licenses/>.
19
 */
20
21
namespace MikoPBX\Core\Asterisk\Configs\Generators\Extensions;
22
23
use MikoPBX\Common\Models\IncomingRoutingTable;
24
use MikoPBX\Common\Models\PbxSettings;
25
use MikoPBX\Common\Models\SoundFiles;
26
use MikoPBX\Core\Asterisk\Configs\AsteriskConfigInterface;
27
use MikoPBX\Core\Asterisk\Configs\ConferenceConf;
28
use MikoPBX\Core\Asterisk\Configs\AsteriskConfigClass;
29
use MikoPBX\Core\Asterisk\Configs\ExtensionsConf;
30
use MikoPBX\Core\System\Util;
31
32
/**
33
 * This class generates incoming contexts for Asterisk configuration.
34
 *
35
 * @package MikoPBX\Core\Asterisk\Configs\Generators\Extensions
36
 */
37
class IncomingContexts extends AsteriskConfigClass
38
{
39
    // The module hook applying priority.
40
    public int $priority = 530;
41
42
    // @var array|string $provider Provider information for routing.
43
    public array|string $provider;
44
45
    // @var string $login Login information.
46
    public string $login;
47
48
    // @var string $uniqId Unique ID for the dialplan.
49
    public string $uniqId;
50
51
    // @var array $dialplan Dialplan information.
52
    private array $dialplan = [];
53
54
    // @var array $rout_data_dial Stores the generated dialplan commands.
55
    private array $rout_data_dial = [];
56
57
    // @var array $confExtensions Configuration extensions.
58
    private array $confExtensions = [];
59
60
    // @var array $routes Routes for handling dialplan generation.
61
    private array $routes = [];
62
63
    // The language value.
64
    private string $lang;
65
66
    // @var bool $need_def_rout Flag for needing a default route.
67
    private bool $need_def_rout;
68
69
    /**
70
     * Generate incoming contexts.
71
     *
72
     * @param string|array $provider
73
     * @param string $login
74
     * @param string $uniqId
75
     *
76
     * @return string
77
     */
78
    public static function generate(array|string $provider, string $login = '', string $uniqId = ''): string
79
    {
80
        $generator           = new self();
81
        $generator->provider = $provider;
82
        $generator->login    = $login;
83
        $generator->uniqId   = $uniqId;
84
        $generator->getSettings();
85
86
        return $generator->makeDialplan();
87
    }
88
89
90
    /**
91
     * Get the settings for generating incoming contexts.
92
     *
93
     * This method retrieves the necessary settings for generating the incoming contexts. It sets the conference
94
     * extensions, routes, language, and the need for a default route based on the configuration.
95
     *
96
     * @return void
97
     */
98
    public function getSettings(): void
99
    {
100
        $this->confExtensions = ConferenceConf::getConferenceExtensions();
101
        $this->routes         = $this->getRoutes();
102
        $this->lang           = str_replace('_', '-', PbxSettings::getValueByKey(PbxSettings::PBX_LANGUAGE));
103
        $this->need_def_rout  = $this->checkNeedDefRout();
104
    }
105
106
    /**
107
     * Get the routes for generating incoming contexts.
108
     *
109
     * This method retrieves the routes based on the provider configuration. It queries the IncomingRoutingTable
110
     * model to fetch the relevant routes and orders them by provider, priority, and extension. The resulting data
111
     * is then sorted using the ExtensionsConf class's sortArrayByPriority method.
112
     *
113
     * @return array The array of routes.
114
     */
115
    private function getRoutes(): array
116
    {
117
        if ('none' === $this->provider) {
118
            // Calls via SIP URI.
119
            $filter = ['provider IS NULL AND priority<>9999', 'order' => 'provider,priority,extension',];
120
        } elseif (is_array($this->provider)) {
121
            $filter = [
122
                'provider IN ({provider:array})',
123
                'bind'  => ['provider' => array_keys($this->provider),],
124
                'order' => 'provider,priority,extension',
125
            ];
126
        } else {
127
            // Calls via provider.
128
            $filter = ["provider = '$this->provider'", 'order' => 'provider,priority,extension',];
129
        }
130
131
        $m_data = IncomingRoutingTable::find($filter);
132
        $data   = $m_data->toArray();
133
        uasort($data, ExtensionsConf::class . '::sortArrayByPriority');
134
135
        return $data;
136
    }
137
138
    /**
139
     * Check if a default route is needed for the provider.
140
     * Populate the routes table with the default value.
141
     *
142
     * This method checks if a default route is required for the provider. If the need for a default route is true
143
     * and the provider is not 'none', a default route entry with empty values for number, extension, and timeout
144
     * is added to the routes array.
145
     *
146
     * @return bool The flag indicating if a default route is needed.
147
     */
148
    private function checkNeedDefRout(): bool
149
    {
150
        $need_def_rout = $this->needDefRout();
151
        if ($need_def_rout === true && 'none' !== $this->provider) {
152
            $this->routes[] = ['number' => '', 'extension' => '', 'timeout' => ''];
153
        }
154
155
        return $need_def_rout;
156
    }
157
158
    /**
159
     * Check if a default route is needed for the provider.
160
     *
161
     * This method checks if a default route is needed for the provider by iterating through the routes array
162
     * and examining the 'number' field of each route. If any route has a number value of 'X!' or an empty string,
163
     * it indicates that a default route is not needed.
164
     *
165
     * @return bool The flag indicating if a default route is needed.
166
     */
167
    private function needDefRout(): bool
168
    {
169
        $needDefRout = true;
170
        foreach ($this->routes as $rout) {
171
            $number = trim($rout['number']);
172
            if ($number === 'X!' || $number === '') {
173
                $needDefRout = false;
174
                break;
175
            }
176
        }
177
178
        return $needDefRout;
179
    }
180
181
    /**
182
     * Generate the dialplan for incoming contexts.
183
     *
184
     * This method generates the dialplan for incoming contexts by iterating through the routes and invoking
185
     * the necessary methods to generate the route dialplan and dial actions. It then multiplies extensions
186
     * in the dialplan, trims the dialplan, and creates the summary dialplan.
187
     *
188
     * @return string The generated dialplan for incoming contexts.
189
     */
190
    public function makeDialplan(): string
191
    {
192
        foreach ($this->routes as $rout) {
193
            $this->generateRouteDialplan($rout);
194
            $this->generateDialActions($rout);
195
        }
196
        $this->multiplyExtensionsInDialplan();
197
        $this->trimDialPlan();
198
199
        return $this->createSummaryDialplan();
200
    }
201
202
    /**
203
     * Generates the first part (header) of an incoming context.
204
     *
205
     * @param array $rout Array containing route information.
206
     */
207
    private function generateRouteDialplan(array $rout): void
208
    {
209
        // Prepare the route number.
210
        $number      = trim($rout['number']);
211
        $rout_number = ($number === '') ? 'X!' : $number;
212
        $rout_data   = &$this->dialplan[$rout_number];
213
214
        // If route data is not empty, exit the function.
215
        if (! empty($rout_data)) {
216
            return;
217
        }
218
219
        // Determine extension prefix based on the route number.
220
        if (mb_strpos($rout_number, '_') === 0) {
221
            $ext_prefix = '';
222
        } elseif (preg_match_all('/^[.|!|N|X|Z|0-9|\[|\]|\-]+$/m', $rout_number, $matches, PREG_SET_ORDER) === 1) {
223
            // Likely an EXTEN template.
224
            $ext_prefix = '_';
225
        } else {
226
            $ext_prefix = '';
227
        }
228
229
        // Generate dialplan strings.
230
        $rout_data .= "exten => $ext_prefix$rout_number,1,NoOp(--- Incoming call ---)\n\t";
231
        $rout_data .= 'same => n,Set(CHANNEL(language)=' . $this->lang . ')' . "\n\t";
232
        $rout_data .= 'same => n,Set(CHANNEL(hangup_handler_wipe)=hangup_handler,s,1)' . "\n\t";
233
        $rout_data .= 'same => n,Set(__FROM_DID=${EXTEN})' . "\n\t";
234
        $rout_data .= 'same => n,Set(__FROM_CHAN=${CHANNEL})' . "\n\t";
235
        $rout_data .= 'same => n,Set(__M_CALLID=${CHANNEL(callid)})' . "\n\t";
236
237
        // Set peer name.
238
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" != "Local"]?Gosub(set_from_peer,s,1))' . "\n\t";
239
        $rout_data .= 'same => n,ExecIf($["${CHANNEL(channeltype)}" == "Local"]?Set(__FROM_PEER=${CALLERID(num)}))' . "\n\t";
240
        $rout_data .= 'same => n,Gosub(add-trim-prefix-clid,${EXTEN},1)' . "\n\t";
241
242
        // Prohibit caller redirection.
243
        $rout_data .= 'same => n,Set(__TRANSFER_OPTIONS=t)' . "\n";
244
        $rout_data .= $this->hookModulesMethod(AsteriskConfigInterface::GENERATE_INCOMING_ROUT_BEFORE_DIAL_PRE_SYSTEM, [$rout_number]);
245
        $rout_data .= $this->hookModulesMethod(AsteriskConfigInterface::GENERATE_INCOMING_ROUT_BEFORE_DIAL_SYSTEM, [$rout_number]);
246
        $rout_data .= $this->hookModulesMethod(AsteriskConfigInterface::GENERATE_INCOMING_ROUT_BEFORE_DIAL, [$rout_number]);
247
248
        // Describe the ability to jump into the custom sub context.
249
        $rout_data .= " \n\t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-custom,${EXTEN},1)';
250
251
        if (! empty($rout['extension'])) {
252
            $rout_data = rtrim($rout_data);
253
        }
254
    }
255
256
    /**
257
     * Generates dial commands (goto dial) in the Dialplan.
258
     *
259
     * @param array $rout Array containing route information.
260
     *
261
     * @return void
262
     */
263
    private function generateDialActions(array $rout): void
264
    {
265
        // If the route doesn't have an extension, exit the function.
266
        if (empty($rout['extension'])) {
267
            return;
268
        }
269
        // Prepare the route number.
270
        $number      = trim($rout['number']);
271
        $rout_number = ($number === '') ? 'X!' : $number;
272
273
        // Generate dial actions based on route number.
274
        $this->generateDialActionsRoutNumber($rout, $rout_number, $number);
275
    }
276
277
    /**
278
     * Generate dialplan commands for the given route and route number.
279
     *
280
     * This function creates the dialplan commands for routing calls to different
281
     * extensions based on the provided route configuration and route number.
282
     *
283
     * @param array $rout The route configuration.
284
     * @param string $rout_number The route number.
285
     * @param string $number The number to dial.
286
     * @return void
287
     */
288
    private function generateDialActionsRoutNumber(array $rout, string $rout_number, string $number): void
289
    {
290
        // Set the timeout for the route based on the configuration.
291
        $timeout = trim($rout['timeout']);
292
293
        // Check for the existence of the dial status in the route data.
294
        // This is required for handling parking calls through AMI.
295
        if (! isset($this->rout_data_dial[$rout_number])) {
296
            $this->rout_data_dial[$rout_number] = '';
297
        }
298
299
        // Check if the extension is a conference extension.
300
        if (in_array($rout['extension'], $this->confExtensions, true)) {
301
            // For conference extensions, there's no need to handle answer timeout.
302
            // The call will be answered immediately by the conference.
303
            $dialplanCommands = " \n\t" . 'same => n,ExecIf($["${M_DIALSTATUS}" != "ANSWER" && "${M_DIALSTATUS}" != "BUSY"]?' . "Goto(internal,{$rout['extension']},1));";
304
        } else {
305
            // For other extensions, handle the answer timeout and generate the appropriate dial command.
306
            $dialplanCommands = " \n\t" . "same => n,Set(M_TIMEOUT=$timeout)" .
307
                                " \n\t" . 'same => n,ExecIf($["${M_DIALSTATUS}" != "ANSWER" && "${M_DIALSTATUS}" != "BUSY"]?' . "Dial(Local/{$rout['extension']}@internal-incoming,$timeout," . '${TRANSFER_OPTIONS}' . "Kg));";
308
        }
309
310
        $mediaFile = $this->getSoundFilePath($rout);
311
        if (!empty($mediaFile)) {
312
            $this->rout_data_dial[$rout_number] .= " \n\t" . 'same => n,ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?Playback(' . $mediaFile . '));' . PHP_EOL;
313
        }
314
315
        // Add the generated commands to the route data.
316
        $this->rout_data_dial[$rout_number] .= $dialplanCommands;
317
318
        // Handle duplicate dial actions for the route.
319
        $this->duplicateDialActionsRoutNumber($rout, $dialplanCommands, $number);
320
    }
321
322
    /**
323
     * Get path ro sound file
324
     * @param array $rout
325
     * @return string
326
     */
327
    public function getSoundFilePath(array $rout): string
328
    {
329
        if (empty($rout['audio_message_id'])) {
330
            return '';
331
        }
332
        $res           = SoundFiles::findFirst($rout['audio_message_id']);
333
        $audio_message = $res === null ? '' : (string)$res->path;
334
        if (file_exists($audio_message)) {
335
            return Util::trimExtensionForFile($audio_message);
336
        }
337
        return '';
338
    }
339
340
    /**
341
     * Handle duplicate dial actions for the given route.
342
     *
343
     * This function adds the generated dialplan commands for routing calls to different
344
     * extensions based on the provided route configuration. It handles specifically the
345
     * case where the extension is 'login'.
346
     *
347
     * @param array $rout The route configuration.
348
     * @param string $dial_command The dial command to be added.
349
     * @param string $number The number to dial.
350
     * @return void
351
     */
352
    private function duplicateDialActionsRoutNumber(array $rout, string $dial_command, string $number): void
353
    {
354
        // Check if the provider array is properly set.
355
        if (! is_array($this->provider)) {
356
            return;
357
        }
358
359
        // Get the provider key from the route configuration.
360
        $key = $this->provider[$rout['provider']] ?? '';
361
362
        // Check for the existence of the dial status in the route data.
363
        if (! isset($this->rout_data_dial[$key])) {
364
            $this->rout_data_dial[$key] = '';
365
        }
366
367
        // If the number is empty, add the dial command to the route data.
368
        if (empty($number)) {
369
            $this->rout_data_dial[$key] .= $dial_command;
370
        }
371
    }
372
373
    /**
374
     * Add additional extensions to the dialplan.
375
     *
376
     * This function modifies the dialplan to handle multiple extensions based on the
377
     * provider information. If the login is a string, it calls a separate function to handle it.
378
     *
379
     * @return void
380
     */
381
    private function multiplyExtensionsInDialplan(): void
382
    {
383
        // If the login is a string, call the specific function to handle it.
384
        if (!empty($this->login)) {
385
            $this->multiplyExtensionsInDialplanStringLogin();
386
        }
387
388
        // If the provider is an array, iterate over it and add each provider to the dialplan.
389
        if (is_array($this->provider)) {
390
            foreach (array_values($this->provider) as $_login) {
391
                // If the login is empty, skip to the next iteration.
392
                if (empty($_login)) {
393
                    continue;
394
                }
395
396
                // Replace the '_X!,1' with the login in the dialplan and assign it to the login key.
397
                $this->dialplan[$_login] = str_replace('_X!,1', "$_login,1", $this->dialplan['X!']);
398
            }
399
        }
400
    }
401
402
    /**
403
     * Adds extensions to the dialplan for a string login.
404
     *
405
     * This function modifies the dialplan and routing data based on the login and add_login_pattern flag.
406
     * It checks if multiple routes are possible or if only a default route is required.
407
     *
408
     * @return void
409
     */
410
    private function multiplyExtensionsInDialplanStringLogin(): void
411
    {
412
        $add_login_pattern = $this->needAddLoginExtension();
413
        if ($this->isMultipleRoutes($add_login_pattern)) {
414
            $this->dialplan[$this->login]       = str_replace('_X!,1', "$this->login,1", $this->dialplan['X!']);
415
            $this->rout_data_dial[$this->login] = $this->rout_data_dial['X!'];
416
        } elseif ($this->defaultRouteOnly($add_login_pattern)) {
417
            // Only default route required.
418
            $this->dialplan[$this->login] = str_replace('_X!,1', "$this->login,1", $this->dialplan['X!']);
419
        }
420
    }
421
422
    /**
423
     * Determines if a login extension needs to be added
424
     *
425
     * This method checks if the login is not empty and is not a numeric value.
426
     * If the login is a numeric value or matches with any of the route numbers,
427
     * no additional login extension is required, so it returns false.
428
     * If the login is not empty, and doesn't match with any of the route numbers,
429
     * it implies an additional login extension is needed, so it returns true.
430
     *
431
     * @return bool Indicates if an additional login extension is needed
432
     */
433
    private function needAddLoginExtension(): bool
434
    {
435
        // Check if the login is not empty
436
        $add_login_pattern = ! empty($this->login);
437
438
        // Iterate through the routes
439
        foreach ($this->routes as $rout) {
440
            // If the login is empty, no additional login extension is required
441
            if (! $add_login_pattern) {
442
                break;
443
            }
444
445
            // Check if the login is a numeric value
446
            $is_num = preg_match_all('/^\d+$/m', $this->login, $matches, PREG_SET_ORDER);
447
            if ($is_num === 1) {
448
                // If the login is a numeric value, no additional login extension is required
449
                $add_login_pattern = false;
450
                break;
451
            }
452
453
            // If the route number does not match with the login, continue to the next route
454
            if (trim($rout['number']) !== $this->login) {
455
                continue;
456
            }
457
458
            // If the route number matches with the login, no additional login extension is required
459
            $add_login_pattern = false;
460
            break;
461
        }
462
463
        // Return whether an additional login extension is needed
464
        return $add_login_pattern;
465
    }
466
467
    /**
468
     * Check if multiple routes are possible.
469
     *
470
     * @param bool $add_login_pattern Flag indicating if an additional extension is required.
471
     *
472
     * @return bool Returns true if multiple routes are possible, false otherwise.
473
     */
474
    private function isMultipleRoutes(bool $add_login_pattern): bool
475
    {
476
        return $add_login_pattern && array_key_exists('X!', $this->rout_data_dial) && isset($this->dialplan['X!']);
477
    }
478
479
    /**
480
     * Check if only a default route is required.
481
     *
482
     * @param bool $add_login_pattern Flag indicating if an additional extension is required.
483
     *
484
     * @return bool Returns true if only a default route is required, false otherwise.
485
     */
486
    private function defaultRouteOnly(bool $add_login_pattern): bool
487
    {
488
        return $add_login_pattern === true && $this->need_def_rout === true && count($this->routes) === 1;
489
    }
490
491
    /**
492
     * Trim spaces on the right of the dialplan.
493
     *
494
     * This function trims spaces from the
495
     * right of each dialplan and appends the corresponding routing data.
496
     *
497
     * @return void
498
     */
499
    private function trimDialPlan(): void
500
    {
501
        foreach ($this->dialplan as $key => &$dialplan) {
502
            if (! array_key_exists($key, $this->rout_data_dial)) {
503
                continue;
504
            }
505
            $dialplan = rtrim($dialplan);
506
            $dialplan .= $this->rout_data_dial[$key];
507
        }
508
    }
509
510
    /**
511
     * * Creates a summary dialplan
512
     *
513
     * This method constructs a summary dialplan by retrieving the default action from the incoming routing table.
514
     * It creates an ID based on the provider or unique ID, initializes the dialplan with the ID, and
515
     * iterates through the dialplan to generate each line of the dialplan. If a default action is found,
516
     * it is also included in the dialplan. The dialplan ends with a 'Hangup' action.
517
     *
518
     * @return string The constructed dialplan
519
     */
520
    private function createSummaryDialplan(): string
521
    {
522
        // Find the default action in the incoming routing table
523
        /** @var IncomingRoutingTable $default_action */
524
        $default_action = IncomingRoutingTable::findFirst('priority = 9999');
525
526
        // Create an ID based on the provider or unique ID
527
        $id = is_string($this->provider) ? $this->provider : $this->uniqId;
528
529
        // Initialize the dialplan with the ID
530
        $conf = "\n" . "[$id-incoming]\n";
531
532
        // Iterate through the dialplan
533
        foreach ($this->dialplan as $dpln) {
534
            // Add each line of the dialplan
535
            $conf .= $dpln . "\n";
536
537
            // If a default action is found, include it in the dialplan
538
            if (null !== $default_action) {
539
                $conf .= $this->createSummaryDialplanDefAction($default_action, $id);
540
            }
541
542
            // End the dialplan with a 'Hangup' action
543
            $conf .= "\t" . "same => n,Hangup()" . "\n";
544
        }
545
546
        // Return the constructed dialplan
547
        return $conf;
548
    }
549
550
    /**
551
     * Creates a summary dialplan with default action
552
     *
553
     * This method constructs the dialplan depending on the default action provided.
554
     * If the action is an 'extension', the method sets the dialplan to go to a different extension and checks
555
     * if the 'after-dial-custom' exists in the dialplan. If the action is 'busy', the dialplan is set to busy.
556
     *
557
     * @param IncomingRoutingTable $default_action The default action for the dialplan
558
     * @param string $uniqId Unique ID for identification
559
     *
560
     * @return string The constructed dialplan
561
     */
562
    private function createSummaryDialplanDefAction(IncomingRoutingTable $default_action, string $uniqId): string
563
    {
564
        // Initialize the dialplan
565
        $conf = '';
566
        // If the action is an 'extension', modify the dialplan accordingly
567
        if ('extension' === $default_action->action) {
568
            // Set the dialplan to go to a different extension
569
            $conf = $this->createSummaryDialplanGoto($conf, $default_action, $uniqId);
570
571
            // Check if the 'after-dial-custom' exists in the dialplan
572
            $conf .= " \t" . 'same => n,GosubIf($["${DIALPLAN_EXISTS(${CONTEXT}-after-dial-custom,${EXTEN},1)}" == "1"]?${CONTEXT}-after-dial-custom,${EXTEN},1)' . "\n";
573
574
            // Check if the dial status is 'busy'
575
            $conf .= " \t" . 'same => n,ExecIf($["${M_DIALSTATUS}" == "BUSY"]?Busy(2));' . PHP_EOL;
576
577
        // If the action is 'playback', hangup call
578
        } elseif (IncomingRoutingTable::ACTION_PLAYBACK === $default_action->action) {
579
            $mediaFile = $this->getSoundFilePath($default_action->toArray());
580
            if (!empty($mediaFile)) {
581
                $conf .= " \n\t" . 'same => n,ExecIf($["${M_DIALSTATUS}" != "ANSWER"]?Playback(' . $mediaFile . '));' . PHP_EOL;
582
            }
583
        // If the action is 'busy', set the dialplan to busy
584
        } elseif (IncomingRoutingTable::ACTION_BUSY === $default_action->action) {
585
            $conf .= "\t" . "same => n,Busy(2)" . PHP_EOL;
586
        }
587
588
        // Return the constructed dialplan
589
        return $conf;
590
    }
591
592
    /**
593
     * This function generates a part of an Asterisk dialplan. It handles the Goto action for the dialplan,
594
     * checking the DIALSTATUS, and if necessary, redirects the call to a conference or dials the extension.
595
     *
596
     * @param string $conf The existing dialplan configuration string, which will be appended to.
597
     * @param IncomingRoutingTable $default_action The default action of the routing table, which provides the
598
     *                                             extension that should be dialed or the conference that should be joined.
599
     * @param string $uniqId Unique identifier for the provider or incoming route.
600
     *
601
     * @return string Returns the updated dialplan configuration.
602
     */
603
    private function createSummaryDialplanGoto(
604
        string $conf,
605
        IncomingRoutingTable $default_action,
606
        string $uniqId
607
    ): string {
608
        // DIALSTATUS must be checked, especially when dealing with parking lot calls through AMI.
609
        // The next priority might be executed upon answering.
610
        $conf .= "\t" . 'same => n,Set(M_TIMEOUT=0)' . "\n";
611
        if (in_array($default_action->extension, $this->confExtensions, true)) {
612
            // This is a conference. No need to handle answer timeout here.
613
            // The call will be immediately answered by the conference.
614
            $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER" && "${M_DIALSTATUS}" != "BUSY"]?' . "Goto(internal,$default_action->extension,1)); default action" . "\n";
615
        } else {
616
            // Dial the local extension if the DIALSTATUS is not ANSWER or BUSY.
617
            $conf .= "\t" . "same => n," . 'ExecIf($["${M_DIALSTATUS}" != "ANSWER" && "${M_DIALSTATUS}" != "BUSY"]?' . "Dial(Local/$default_action->extension@internal,," . '${TRANSFER_OPTIONS}' . "Kg)); default action" . "\n";
618
        }
619
620
        // Execute the hook method for generating the context after the dial.
621
        $conf .= $this->hookModulesMethod(AsteriskConfigInterface::GENERATE_INCOMING_ROUT_AFTER_DIAL_CONTEXT, [$uniqId]);
622
623
        return $conf;
624
    }
625
}
626