AsteriskManager::Originate()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 18
c 0
b 0
f 0
dl 0
loc 37
rs 9.6666
cc 4
nc 6
nop 12

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

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;
22
23
use MikoPBX\Core\System\Processes;
24
use MikoPBX\Core\System\SystemMessages;
25
use MikoPBX\Core\System\Util;
26
use Throwable;
27
28
/**
29
 * Asterisk Manager class
30
 *
31
 * @link    http://www.voip-info.org/wiki-Asterisk+config+manager.conf
32
 * @link    http://www.voip-info.org/wiki-Asterisk+manager+API
33
 * @example examples/sip_show_peer.php Get information about a sip peer
34
 * @package phpAGI
35
 */
36
class AsteriskManager
37
{
38
    /**
39
     * Config variables
40
     *
41
     * @var array
42
     * @access public
43
     */
44
    public array $config;
45
46
    /** @var string  */
47
    private string $listenEvents;
48
49
    /**
50
     * Socket
51
     *
52
     * @var resource|false $socket
53
     * @access public
54
     */
55
    public $socket = false;
56
57
    /**
58
     * Server we are connected to
59
     *
60
     * @access public
61
     * @var string
62
     */
63
    public string $server;
64
65
    /**
66
     * Port on the server we are connected to
67
     *
68
     * @access public
69
     * @var int
70
     */
71
    public int $port;
72
73
    /**
74
     * Parent AGI
75
     *
76
     * @access public
77
     * @var AGI
78
     */
79
    public AGI $pagi;
80
81
    /**
82
     * Event Handlers
83
     *
84
     * @access private
85
     * @var array
86
     */
87
    private array $event_handlers;
88
89
    /**
90
     * Whether we're successfully logged in
91
     *
92
     * @access private
93
     * @var bool
94
     */
95
    private bool $_loggedIn = false;
96
97
    /**
98
     * Constructor
99
     *
100
     * @param ?string $config    is the name of the config file to parse or a parent agi from which to read the config
101
     * @param array  $optconfig is an array of configuration vars and vals, stuffed into $this->config['asmanager']
102
     */
103
    public function __construct(?string $config = null, array $optconfig = [])
104
    {
105
        // load config
106
        if (!is_null($config) && file_exists($config)) {
107
            $arrData = parse_ini_file($config, true);
108
            $this->config = ($arrData === false) ? [] : $arrData;
109
        }
110
111
        // If optconfig is specified, stuff vals and vars into 'asmanager' config array.
112
        foreach ($optconfig as $var => $val) {
113
            $this->config['asmanager'][$var] = $val;
114
        }
115
116
        // add default values to config for uninitialized values
117
        if (! isset($this->config['asmanager']['server'])) {
118
            $this->config['asmanager']['server'] = 'localhost';
119
        }
120
        if (! isset($this->config['asmanager']['port'])) {
121
            $this->config['asmanager']['port'] = 5038;
122
        }
123
        if (! isset($this->config['asmanager']['username'])) {
124
            $this->config['asmanager']['username'] = 'phpagi';
125
        }
126
        if (! isset($this->config['asmanager']['secret'])) {
127
            $this->config['asmanager']['secret'] = 'phpagi';
128
        }
129
    }
130
131
    /**
132
     * Ping the AMI listener and wait for a response.
133
     *
134
     * @param string $pingTube The name of the ping tube.
135
     * @return bool True if a response is received within the timeout, false otherwise.
136
     */
137
    public function pingAMIListener(string $pingTube = 'CdrConnector'): bool
138
    {
139
        // Set event filter.
140
        $params = ['Operation' => 'Add', 'Filter' => 'Event: UserEvent'];
141
        $this->sendRequestTimeout('Filter', $params);
142
        // Send the ping.
143
        $req        = '';
144
        $parameters = [
145
            'Action'    => 'UserEvent',
146
            'UserEvent' => $pingTube,
147
        ];
148
        foreach ($parameters as $var => $val) {
149
            $req .= "$var: $val\r\n";
150
        }
151
        $req .= "\r\n";
152
        if (! is_resource($this->socket)) {
153
            return false;
154
        }
155
        $this->sendDataToSocket($req);
156
157
        // Measure the time.
158
        $time_start = $this->microtimeFloat();
159
        $result     = false;
160
        $timeout    = false;
161
162
        // Listen for events and wait for a response.
163
        do {
164
            $type       = '';
165
            $parameters = [];
166
            if (! is_resource($this->socket)) {
167
                return false;
168
            }
169
            $buffer = $this->getStringDataFromSocket();
170
            while (!empty($buffer)) {
171
                $a = strpos($buffer, ':');
172
                if ($a) {
173
                    if (! count($parameters)) {
174
                        $type = strtolower(substr($buffer, 0, $a));
175
                    }
176
                    $parameters[substr($buffer, 0, $a)] = substr($buffer, $a + 2);
177
                }
178
                $buffer = $this->getStringDataFromSocket();
179
            }
180
181
            if ($type === '' && count($this->ping()) === 0) {
182
                $timeout = true;
183
            } elseif (
184
                'event' === $type
185
                && $parameters['Event'] === 'UserEvent'
186
                && "{$pingTube}Pong" === $parameters['UserEvent']
187
            ) {
188
                // Response received.
189
                $result = true;
190
                break;
191
            }
192
            $time = $this->microtimeFloat() - $time_start;
193
            if ($time > 5) {
194
                // Timeout reached.
195
                break;
196
            }
197
        } while (! $timeout);
198
199
        return $result;
200
    }
201
202
    /**
203
     * Send a request with timeout to the socket.
204
     *
205
     * @param string $action The action to perform.
206
     * @param array $parameters The parameters for the request (optional).
207
     * @return array The response from the socket.
208
     */
209
    public function sendRequestTimeout(string $action, array $parameters = []): array
210
    {
211
        if (! is_resource($this->socket) && !$this->connectDefault()) {
212
            return [];
213
        }
214
        // Set the mandatory fields.
215
        $parameters['Action']   = $action;
216
        $parameters['ActionID'] = $parameters['ActionID'] ?? "{$action}_" . getmypid();
217
        $req = "";
218
        foreach ($parameters as $var => $val) {
219
            $req .= "$var: $val\r\n";
220
        }
221
        $req .= "\r\n";
222
223
        $result = $this->sendDataToSocket($req);
224
        if (!$result) {
225
            usleep(500000);
226
            if ($this->connectDefault()) {
227
                $result = $this->sendDataToSocket($req);
228
            }
229
        }
230
231
        $response = [];
232
        if ($result) {
233
            $response = $this->waitResponse(true);
234
        }
235
        return $response;
236
    }
237
238
    /**
239
     * Connect to the default socket.
240
     *
241
     * @return bool True if the connection to the default socket is successful and logged in, false otherwise.
242
     */
243
    private function connectDefault(): bool
244
    {
245
        $this->connect(null, null, null, $this->listenEvents);
246
        return $this->loggedIn();
247
    }
248
249
    /**
250
     * Wait for a response
251
     *
252
     * If a request was just sent, this will return the response.
253
     * Otherwise, it will loop forever, handling events.
254
     *
255
     * @param bool $allow_timeout if the socket times out, return an empty array
256
     *
257
     * @return array of parameters, empty on timeout
258
     */
259
    public function waitResponse(bool $allow_timeout = false): array
260
    {
261
        $timeout = false;
262
        do {
263
            $type       = '';
264
            $parameters = [];
265
            $response   = [];
266
            if (!$this->waitResponseGetInitialData($response)) {
267
                return $parameters;
268
            }
269
            if (isset($response['data']) && empty($response['data']) && !$this->waitResponseGetInitialData($response)) {
270
                return $parameters;
271
            }
272
            $buffer = $response['data'] ?? '';
273
            while ($buffer !== '') {
274
                $a = strpos($buffer, ':');
275
                if ($a) {
276
                    $event_text = substr($buffer, $a + 2);
277
                    $this->waitResponseGetEventType($parameters, $buffer, $a, $type);
278
                    $this->waitResponseReadFollowsPart($event_text, $parameters);
279
                    $this->waitResponseReadCompletePart($event_text, $parameters);
280
                    $parameters[substr($buffer, 0, $a)] = $event_text;
281
                }
282
                $buffer = $this->getStringDataFromSocket();
283
            }
284
            $this->waitResponseProcessResponse($type, $timeout, $allow_timeout, $parameters);
285
        } while ($type !== 'response' && ! $timeout);
286
287
        return $parameters;
288
    }
289
290
    /**
291
     * Get the initial data from the socket response.
292
     *
293
     * @param array $response The variable to store the response data.
294
     * @return bool True if the initial data was obtained successfully, false otherwise.
295
     */
296
    private function waitResponseGetInitialData(array &$response): bool
297
    {
298
        if (!is_resource($this->socket) && !$this->connectDefault()) {
299
            return false;
300
        }
301
        $result = true;
302
        $response = $this->getDataFromSocket();
303
        if (isset($response['error'])) {
304
            usleep(500000);
305
            if ($this->connectDefault()) {
306
                $response = $this->getDataFromSocket();
307
            }
308
        }
309
        if (isset($response['error'])) {
310
            $result = false;
311
        }
312
        return $result;
313
    }
314
315
    /**
316
     * Process the response based on the event type.
317
     *
318
     * @param string $type The event type.
319
     * @param bool $timeout The timeout.
320
     * @param bool $allow_timeout Indicates whether the timeout is allowed.
321
     * @param array $parameters The parameters containing the sub-data.
322
     * @return void
323
     */
324
    private function waitResponseProcessResponse(string $type, bool &$timeout, bool $allow_timeout, array $parameters): void
325
    {
326
        switch ($type) {
327
            case '':
328
                // Timeout occured
329
                $timeout = $allow_timeout;
330
                break;
331
            case 'event':
332
                $this->processEvent($parameters);
333
                break;
334
            case 'response':
335
                break;
336
        }
337
    }
338
339
    /**
340
     * Get the event type from the response buffer.
341
     *
342
     * @param array $parameters The parameters containing the sub-data.
343
     * @param string $buffer The response buffer.
344
     * @param int $a The position of the colon character in the buffer.
345
     * @param string $type The variable to store the event type.
346
     * @return void
347
     */
348
    private function waitResponseGetEventType(array $parameters, string $buffer, int $a, string &$type): void
349
    {
350
        if (! count($parameters)) {
351
            $type = strtolower(substr($buffer, 0, $a));
352
        }
353
    }
354
355
    /**
356
     * Wait for the follows part of a specific event and retrieve the data.
357
     *
358
     * @param string $event_text The text of the event.
359
     * @param array $parameters The parameters to store the data.
360
     * @return void
361
     */
362
    private function waitResponseReadFollowsPart(string $event_text, array &$parameters): void
363
    {
364
        if (($event_text === 'Follows') && !count($parameters)) {
365
            // A follows response means there is a miltiline field that follows.
366
            $parameters['data'] = '';
367
            $buff               = $this->getStringDataFromSocket();
368
            while (strpos($buff, '--END ') !== 0) {
369
                $parameters['data'] .= $buff;
370
                $buff               = $this->getStringDataFromSocket();
371
            }
372
        }
373
    }
374
375
    /**
376
     * Wait for the complete part of a specific event and retrieve the sub-data.
377
     *
378
     * @param string $event_text The text of the event.
379
     * @param array $parameters The parameters to store the sub-data.
380
     * @return void
381
     */
382
    private function waitResponseReadCompletePart(string $event_text, array &$parameters): void
383
    {
384
        $settings = [
385
            'Queue status will follow'          => ['QueueStatusComplete'],
386
            'Channels will follow'              => ['CoreShowChannelsComplete'],
387
            'Result will follow'                => ['DBGetComplete', 'DBGetTreeComplete'],
388
            'Parked calls will follow'          => ['ParkedCallsComplete'],
389
            'Peer status list will follow'      => ['PeerlistComplete'],
390
            'IAX Peer status list will follow'  => ['PeerlistComplete'],
391
            'Registrations will follow'         => ['RegistrationsComplete'],
392
            'Meetme user list will follow'      => ['MeetmeListComplete'],
393
            'Meetme conferences will follow'    => ['MeetmeListRoomsComplete'],
394
            'Following are Events for each object associated with the Endpoint' => ['EndpointDetailComplete'],
395
            'Following are Events for each Outbound registration'               => ['OutboundRegistrationDetailComplete'],
396
            'A listing of Endpoints follows, presented as EndpointList events'  => ['EndpointListComplete']
397
        ];
398
        if (isset($settings[$event_text])) {
399
            $this->waitResponseGetSubData($parameters, $settings[$event_text]);
400
        }
401
    }
402
403
    /**
404
     * Get data from the socket.
405
     *
406
     * @return array An array containing the data from the socket response or an error message.
407
     */
408
    private function getDataFromSocket(): array
409
    {
410
        $response = [];
411
        if (!is_resource($this->socket)) {
412
            $response['error'] = 'Socket not init.';
413
            return $response;
414
        }
415
        try {
416
            $resultFgets = fgets($this->socket, 4096);
417
            if ($resultFgets !== false) {
418
                $buffer = trim($resultFgets);
419
                $response['data']  = $buffer;
420
            } else {
421
                $response['error'] = 'Read data error.';
422
            }
423
        } catch (Throwable $e) {
424
            $response['error'] = $e->getMessage();
425
        }
426
427
        return $response;
428
    }
429
430
    /**
431
     * Get string data from the socket response.
432
     *
433
     * @return string The string data from the socket response.
434
     */
435
    private function getStringDataFromSocket(): string
436
    {
437
        $response = $this->getDataFromSocket();
438
        return $response['data'] ?? '';
439
    }
440
441
    /**
442
     * Send data to the socket.
443
     *
444
     * @param string $req The data to send.
445
     * @return bool True if the data was sent successfully, false otherwise.
446
     */
447
    private function sendDataToSocket(string $req): bool
448
    {
449
        if (!is_resource($this->socket)) {
450
            return false;
451
        }
452
        $result = true;
453
        try {
454
            $resultWrite = fwrite($this->socket, $req);
455
            if ($resultWrite === false) {
456
                $result = false;
457
            }
458
        } catch (Throwable $e) {
459
            $result = false;
460
        }
461
        return $result;
462
    }
463
464
    /**
465
     * Wait for response and get sub-data from the socket.
466
     *
467
     * @param array $parameters The parameters to store the sub-data.
468
     * @param array $end_string The string indicating the end of the response (optional).
469
     * @return void
470
     */
471
    private function waitResponseGetSubData(array &$parameters, array $end_string): void
472
    {
473
        if (empty($end_string)) {
474
            return;
475
        }
476
        $parameters['data'] = [];
477
        $m                  = [];
478
        do {
479
            $value = '';
480
            $buff  = $this->getStringDataFromSocket() . $value;
481
            $a_pos = strpos($buff, ':');
482
            if (!$a_pos) {
483
                if (empty($m)) {
484
                    continue;
485
                }
486
                if ($parameters['ActionID'] === $m['ActionID']) {
487
                    // This is the event for the last request of the current worker
488
                    if (array_key_exists('Event', $m)) {
489
                        $parameters['data'][$m['Event']][] = $m;
490
                    } else {
491
                        SystemMessages::sysLogMsg(__METHOD__, "No key Event on AMI Answer:" . implode(' ', $m), LOG_DEBUG);
492
                    }
493
                    $m = [];
494
                } elseif (isset($m['Event'])) {
495
                    // These are other events not related to the last request
496
                    $this->processEvent($parameters);
497
                }
498
                continue;
499
            }
500
            $key   = trim(substr($buff, 0, $a_pos));
501
            $value = trim(substr($buff, $a_pos + 1));
502
            $m[$key] = $value;
503
        } while (!in_array($value, $end_string, true));
504
    }
505
506
    /**
507
     * Process event
508
     *
509
     * @access private
510
     *
511
     * @param array $parameters
512
     *
513
     * @return mixed result of event handler or false if no handler was found
514
     */
515
    public function processEvent(array $parameters): mixed
516
    {
517
        $ret = false;
518
        $e   = strtolower($parameters['Event']);
519
520
        $handler = '';
521
        if (isset($this->event_handlers[$e])) {
522
            $handler = $this->event_handlers[$e];
523
        } elseif (isset($this->event_handlers['*'])) {
524
            $handler = $this->event_handlers['*'];
525
        }
526
        if (is_array($handler)) {
527
            call_user_func($handler, $parameters);
528
        } elseif (function_exists($handler)) {
529
            $ret = $handler($e, $parameters, $this->server, $this->port);
530
        }
531
532
        return $ret;
533
    }
534
535
    public function microtimeFloat(): float
536
    {
537
        [$usec, $sec] = explode(" ", microtime());
538
539
        return ((float)$usec + (float)$sec);
540
    }
541
542
    /**
543
     * Ping
544
     *
545
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Ping
546
     */
547
    public function ping(): array
548
    {
549
        return $this->sendRequestTimeout('Ping');
550
    }
551
552
    /**
553
     * Wait for a user events.
554
     *
555
     * @param $allow_timeout bool
556
     *
557
     * @return array of parameters, empty on timeout
558
     */
559
    public function waitUserEvent(bool $allow_timeout = false): array
560
    {
561
        $timeout = false;
562
        do {
563
            $type       = '';
564
            $parameters = [];
565
            $buffer = $this->getStringDataFromSocket();
566
            while ($buffer !== '') {
567
                $pos = strpos($buffer, ':');
568
                if ($pos) {
569
                    if (! count($parameters)) {
570
                        $type = strtolower(substr($buffer, 0, $pos));
571
                    }
572
                    $parameters[substr($buffer, 0, $pos)] = substr($buffer, $pos + 2);
573
                }
574
                $buffer = $this->getStringDataFromSocket();
575
            }
576
            if ($type === '' && count($this->ping()) === 0) {
577
                $timeout = $allow_timeout;
578
            } elseif (stripos($type, 'event') !== false) {
579
                $this->processEvent($parameters);
580
            }
581
        } while (! $timeout);
582
583
        return $parameters;
584
    }
585
586
    // *********************************************************************************************************
587
    // **                       COMMANDS                                                                      **
588
    // *********************************************************************************************************
589
590
    /**
591
     * Connect to Asterisk
592
     *
593
     * @param ?string $server
594
     * @param ?string $username
595
     * @param ?string $secret
596
     * @param string $events
597
     *
598
     * @return bool true on success
599
     * @example examples/sip_show_peer.php Get information about a sip peer
600
     *
601
     */
602
    public function connect(string $server = null, string $username = null, string $secret = null, string $events = 'on'): bool
603
    {
604
        $this->listenEvents = $events;
605
        // use config if not specified
606
        if (is_null($server)) {
607
            $server = $this->config['asmanager']['server'];
608
        }
609
        if (is_null($username)) {
610
            $username = $this->config['asmanager']['username'];
611
        }
612
        if (is_null($secret)) {
613
            $secret = $this->config['asmanager']['secret'];
614
        }
615
616
        // get port from server if specified
617
        if (strpos($server, ':') !== false) {
618
            $c            = explode(':', $server);
619
            $this->server = $c[0];
620
            $this->port   = (int)$c[1];
621
        } else {
622
            $this->server = $server;
623
            $this->port   = $this->config['asmanager']['port'];
624
        }
625
626
        // connect the socket
627
        $errno   = $errStr = null;
628
        $timeout = 2;
629
630
        $busyBoxPath = Util::which('busybox');
631
        $chkCommand = "$busyBoxPath netstat -ntap | $busyBoxPath grep '0.0.0.0:$this->port ' | $busyBoxPath grep LISTEN | $busyBoxPath grep asterisk";
632
        if (Processes::mwExec($chkCommand) === 1) {
633
            SystemMessages::sysLogMsg('AMI', "Exceptions, Unable to connect to $server: the asterisk process is not running", LOG_ERR);
634
            return false;
635
        }
636
        try {
637
            $this->socket = fsockopen($this->server, $this->port, $errno, $errStr, $timeout);
638
        } catch (Throwable $e) {
639
            SystemMessages::sysLogMsg('AMI', "Exceptions, Unable to connect to manager $server ($errno): $errStr", LOG_ERR);
640
            return false;
641
        }
642
        if ($this->socket === false) {
643
            SystemMessages::sysLogMsg('AMI', "Unable to connect to manager $server ($errno): $errStr", LOG_ERR);
644
            return false;
645
        }
646
        stream_set_timeout($this->socket, 1, 0);
647
648
        // read the header
649
        $str = $this->getStringDataFromSocket();
650
        if ($str === '') {
651
            // a problem.
652
            SystemMessages::sysLogMsg('AMI', "Asterisk Manager header not received.", LOG_ERR);
653
            return false;
654
        }
655
656
        // login
657
        $res = $this->sendRequest('login', ['Username' => $username, 'Secret' => $secret, 'Events' => $events]);
658
        if ($res['Response'] !== 'Success') {
659
            $this->_loggedIn = false;
660
            SystemMessages::sysLogMsg('AMI', "Failed to login.", LOG_ERR);
661
            $this->disconnect();
662
            return false;
663
        }
664
        $this->_loggedIn = true;
665
666
        return true;
667
    }
668
669
    /**
670
     * Send a request
671
     *
672
     * @param string $action
673
     * @param array $parameters
674
     *
675
     * @return array of parameters
676
     */
677
    public function sendRequest(string $action, array $parameters = []): array
678
    {
679
        $req = "Action: $action\r\n";
680
        foreach ($parameters as $var => $val) {
681
            $req .= "$var: $val\r\n";
682
        }
683
        $req .= "\r\n";
684
        if (! is_resource($this->socket)) {
685
            return [];
686
        }
687
        $this->sendDataToSocket($req);
688
689
        return $this->waitResponse();
690
    }
691
692
    /**
693
     * Disconnect
694
     *
695
     * @example examples/sip_show_peer.php Get information about a sip peer
696
     */
697
    public function disconnect(): void
698
    {
699
        if ($this->_loggedIn === true) {
700
            $this->logoff();
701
        }
702
        if (is_resource($this->socket)) {
703
            fclose($this->socket);
704
        }
705
    }
706
707
    /**
708
     * Logoff Manager
709
     *
710
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Logoff
711
     */
712
    private function logoff(): void
713
    {
714
        $this->sendRequestTimeout('Logoff');
715
    }
716
717
    public function loggedIn(): bool
718
    {
719
        return $this->_loggedIn;
720
    }
721
722
    /**
723
     * Set Absolute Timeout
724
     *
725
     * Hangup a channel after a certain time.
726
     *
727
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+AbsoluteTimeout
728
     *
729
     * @param string $channel Channel name to hangup
730
     * @param int $timeout Maximum duration of the call (sec)
731
     *
732
     * @return array
733
     */
734
    public function AbsoluteTimeout(string $channel, int $timeout): array
735
    {
736
        return $this->sendRequest('AbsoluteTimeout', ['Channel' => $channel, 'Timeout' => $timeout]);
737
    }
738
739
    /**
740
     * Change monitoring filename of a channel
741
     *
742
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ChangeMonitor
743
     *
744
     * @param string $channel the channel to record.
745
     * @param string $file    the new name of the file created in the monitor spool directory.
746
     *
747
     * @return array
748
     */
749
    public function ChangeMonitor(string $channel, string $file): array
750
    {
751
        return $this->sendRequest('ChangeMontior', ['Channel' => $channel, 'File' => $file]);
752
    }
753
754
    /**
755
     * Execute Command
756
     *
757
     * @param string $command
758
     * @param ?string $actionid message matching variable
759
     *
760
     * @return array
761
     * @example examples/sip_show_peer.php Get information about a sip peer
762
     * @link    http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Command
763
     * @link    http://www.voip-info.org/wiki-Asterisk+CLI
764
     */
765
    public function Command(string $command, string $actionid = null): array
766
    {
767
        $parameters = ['Command' => $command];
768
        if ($actionid) {
769
            $parameters['ActionID'] = $actionid;
770
        }
771
772
        return $this->sendRequest('Command', $parameters);
773
    }
774
775
    /**
776
     * Enable/Disable sending of events to this manager
777
     *
778
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Events
779
     *
780
     * @param string $eventMask is either 'on', 'off', or 'system,call,log'
781
     *
782
     * @return array
783
     */
784
    public function Events(string $eventMask): array
785
    {
786
        return $this->sendRequest('Events', ['EventMask' => $eventMask]);
787
    }
788
789
    /**
790
     * Check Extension Status
791
     *
792
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ExtensionState
793
     *
794
     * @param string $exten    Extension to check state on
795
     * @param string $context  Context for extension
796
     * @param ?string $actionid message matching variable
797
     *
798
     * @return array
799
     */
800
    public function ExtensionState(string $exten, string $context, ?string $actionid = null): array
801
    {
802
        $parameters = ['Exten' => $exten, 'Context' => $context];
803
        if ($actionid) {
804
            $parameters['ActionID'] = $actionid;
805
        }
806
807
        return $this->sendRequest('ExtensionState', $parameters);
808
    }
809
810
    /**
811
     * Get the channels information.
812
     *
813
     * @param bool $group Indicates whether to group the channels by Linkedid (optional, default is true).
814
     * @return array The channels information.
815
     */
816
    public function GetChannels(bool $group = true): array
817
    {
818
        $res      = $this->sendRequestTimeout('CoreShowChannels');
819
        $channels = null;
820
        if (isset($res['data']['CoreShowChannel'])) {
821
            $channels = $res['data']['CoreShowChannel'];
822
        }
823
        $channels_id = [];
824
        if (null !== $channels) {
825
            foreach ($channels as $chan) {
826
                if ($group === true) {
827
                    if (! isset($chan['Linkedid'])) {
828
                        continue;
829
                    }
830
                    $channels_id[$chan['Linkedid']][] = $chan['Channel'];
831
                } else {
832
                    $channels_id[] = $chan['Channel'];
833
                }
834
            }
835
        }
836
837
        return $channels_id;
838
    }
839
840
    /**
841
     * Hangup Channel
842
     *
843
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Hangup
844
     *
845
     * @param string $channel The channel name to be hungup
846
     *
847
     * @return array
848
     */
849
    public function Hangup(string $channel): array
850
    {
851
        return $this->sendRequest('Hangup', ['Channel' => $channel]);
852
    }
853
854
    /**
855
     * List IAX Peers
856
     *
857
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+IAXpeers
858
     * @return array
859
     */
860
    public function IAXpeerlist(): array
861
    {
862
        $result   = $this->sendRequestTimeout('IAXpeerlist');
863
        $data     = (isset($result['data']) && is_array($result['data'])) ? $result['data'] : [];
864
        $arr_peer = (isset($data['PeerEntry']) && is_array($data['PeerEntry'])) ? $data['PeerEntry'] : [];
865
866
        return $arr_peer;
867
    }
868
869
    /**
870
     * Get the IAX registry information.
871
     *
872
     * @return array The IAX registry information.
873
     */
874
    public function IAXregistry(): array
875
    {
876
        $result   = $this->sendRequestTimeout('IAXregistry');
877
        $data     = (isset($result['data']) && is_array($result['data'])) ? $result['data'] : [];
878
        $arr_peer = (isset($data['RegistryEntry']) && is_array($data['RegistryEntry'])) ? $data['RegistryEntry'] : [];
879
880
        return $arr_peer;
881
    }
882
883
    /**
884
     * List available manager commands
885
     *
886
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ListCommands
887
     *
888
     * @param string|null $actionid message matching variable
889
     *
890
     * @return array
891
     */
892
    public function ListCommands(?string $actionid = null): array
893
    {
894
        if ($actionid) {
895
            return $this->sendRequest('ListCommands', ['ActionID' => $actionid]);
896
        } else {
897
            return $this->sendRequest('ListCommands');
898
        }
899
    }
900
901
    /**
902
     * Send a UserEvent to Asterisk.
903
     *
904
     * @param string $name The name of the UserEvent.
905
     * @param array $headers The headers for the UserEvent.
906
     * @return array The response from Asterisk.
907
     */
908
    public function UserEvent(string $name, array $headers): array
909
    {
910
        $headers['UserEvent'] = $name;
911
912
        return $this->sendRequestTimeout('UserEvent', $headers);
913
    }
914
915
    /**
916
     * Check Mailbox Message Count
917
     *
918
     * Returns number of new and old messages.
919
     *   Message: Mailbox Message Count
920
     *   Mailbox: <mailboxid>
921
     *   NewMessages: <count>
922
     *   OldMessages: <count>
923
     *
924
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxCount
925
     *
926
     * @param string $mailbox  Full mailbox ID <mailbox>@<vm-context>
927
     * @param string|null $actionid message matching variable
928
     *
929
     * @return array
930
     */
931
    public function MailboxCount(string $mailbox, ?string $actionid = null): array
932
    {
933
        $parameters = ['Mailbox' => $mailbox];
934
        if ($actionid) {
935
            $parameters['ActionID'] = $actionid;
936
        }
937
938
        return $this->sendRequest('MailboxCount', $parameters);
939
    }
940
941
    /**
942
     * Check Mailbox
943
     *
944
     * Returns number of messages.
945
     *   Message: Mailbox Status
946
     *   Mailbox: <mailboxid>
947
     *   Waiting: <count>
948
     *
949
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+MailboxStatus
950
     *
951
     * @param string $mailbox  Full mailbox ID <mailbox>@<vm-context>
952
     * @param string|null $actionid message matching variable
953
     *
954
     * @return array
955
     */
956
    public function MailboxStatus(string $mailbox, ?string $actionid = null): array
957
    {
958
        $parameters = ['Mailbox' => $mailbox];
959
        if ($actionid) {
960
            $parameters['ActionID'] = $actionid;
961
        }
962
963
        return $this->sendRequest('MailboxStatus', $parameters);
964
    }
965
966
    /**
967
     * Monitor a channel
968
     *
969
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor
970
     *
971
     * @param string $channel
972
     * @param string|null $file
973
     * @param string|null $format
974
     * @param bool|null $mix
975
     *
976
     * @return array
977
     */
978
    public function Monitor(string $channel, ?string $file = null, ?string $format = null, ?bool $mix = null): array
979
    {
980
        $parameters = ['Channel' => $channel];
981
        if ($file) {
982
            $parameters['File'] = $file;
983
        }
984
        if ($format) {
985
            $parameters['Format'] = $format;
986
        }
987
        if (! is_null($file)) {
988
            $parameters['Mix'] = ($mix) ? 'true' : 'false';
989
        }
990
991
        return $this->sendRequest('Monitor', $parameters);
992
    }
993
994
    /**
995
     * MixMonitor a channel
996
     *
997
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor
998
     *
999
     * @param string $channel
1000
     * @param string $file
1001
     * @param string $options
1002
     * @param string $command
1003
     * @param string $ActionID
1004
     * @return array
1005
     */
1006
    public function MixMonitor(string $channel, string $file, string $options, string $command = '', string $ActionID = ''): array
1007
    {
1008
        $parameters            = [
1009
            'Channel' => $channel,
1010
            'File'    => $file,
1011
            'options' => $options,
1012
            'ActionID' => $ActionID
1013
        ];
1014
        if (!empty($command)) {
1015
            $parameters['Command'] = $command;
1016
        }
1017
        return $this->sendRequestTimeout('MixMonitor', $parameters);
1018
    }
1019
1020
    /**
1021
     * StopMixMonitor a channel
1022
     *
1023
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Monitor
1024
     *
1025
     * @param string $channel
1026
     * @param string $ActionID
1027
     *
1028
     * @return array
1029
     */
1030
    public function StopMixMonitor(string $channel, string $ActionID = ''): array
1031
    {
1032
        $parameters = [
1033
            'Channel' => $channel,
1034
            'ActionID' => $ActionID
1035
        ];
1036
        return $this->sendRequestTimeout('StopMixMonitor', $parameters);
1037
    }
1038
1039
    /**
1040
     * DBGet a channel
1041
     *
1042
     * @param string $Family
1043
     * @param string $Key
1044
     * @param string $Val
1045
     *
1046
     * @return array
1047
     */
1048
    public function DBPut(string $Family, string $Key, string $Val = ''): array
1049
    {
1050
        $parameters = ['Family' => $Family, 'Key' => $Key, 'Val' => $Val];
1051
        return $this->sendRequestTimeout('DBPut', $parameters);
1052
    }
1053
1054
    /**
1055
     * MeetmeListRooms
1056
     *
1057
     * @param string $ActionID
1058
     *
1059
     * @return array
1060
     */
1061
    public function MeetmeListRooms(string $ActionID = ''): array
1062
    {
1063
        if (empty($ActionID)) {
1064
            $ActionID = Util::generateRandomString(5);
1065
        }
1066
        $parameters = [
1067
            'ActionID' => $ActionID,
1068
        ];
1069
1070
        return $this->sendRequestTimeout('MeetmeListRooms', $parameters);
1071
    }
1072
1073
    /**
1074
     * DBGet a channel
1075
     *
1076
     * @param string $Family
1077
     * @param string $Key
1078
     *
1079
     * @return array
1080
     */
1081
    public function DBGet(string $Family, string $Key): array
1082
    {
1083
        $parameters = ['Family' => $Family, 'Key' => $Key];
1084
1085
        return $this->sendRequestTimeout('DBGet', $parameters);
1086
    }
1087
1088
    /**
1089
     * Originate Call
1090
     *
1091
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Originate
1092
     *
1093
     * @param string|null $channel Channel name to call
1094
     * @param ?string $exten Extension to use (requires 'Context' and 'Priority')
1095
     * @param ?string $context Context to use (requires 'Exten' and 'Priority')
1096
     * @param int|null $priority Priority to use (requires 'Exten' and 'Context')
1097
     * @param ?string $application Application to use
1098
     * @param ?string $data Data to use (requires 'Application')
1099
     * @param ?int $timeout How long to wait for call to be answered (in ms)
1100
     * @param ?string $callerid Caller ID to be set on the outgoing channel
1101
     * @param ?string $variable Channel variable to set (VAR1=value1|VAR2=value2)
1102
     * @param ?string $account Account code
1103
     * @param bool $async true fast origination
1104
     * @param ?string $actionid message matching variable
1105
     *
1106
     * @return array
1107
     */
1108
    public function Originate(
1109
        ?string $channel,
1110
        ?string $exten = null,
1111
        ?string $context = null,
1112
        ?int $priority = null,
1113
        ?string $application = null,
1114
        ?string $data = null,
1115
        ?int $timeout = null,
1116
        ?string $callerid = null,
1117
        ?string $variable = null,
1118
        ?string $account = null,
1119
        bool $async = true,
1120
        ?string $actionid = null
1121
    ): array {
1122
1123
        $parameters = [
1124
            'Exten'         => $exten,
1125
            'Context'       => $context,
1126
            'Priority'      => $priority,
1127
            'Application'   => $application,
1128
            'Data'          => $data,
1129
            'Timeout'       => $timeout,
1130
            'CallerID'      => $callerid,
1131
            'Variable'      => $variable,
1132
            'Account'       => $account,
1133
            'ActionID'      => $actionid
1134
        ];
1135
        $keys = array_keys($parameters);
1136
        foreach ($keys as $key) {
1137
            if (empty($parameters[$key])) {
1138
                unset($parameters[$key]);
1139
            }
1140
        }
1141
        $parameters['Channel'] = $channel;
1142
        $parameters['Async']   = ($async === true) ? 'true' : 'false';
1143
1144
        return $this->sendRequest('Originate', $parameters);
1145
    }
1146
1147
    /**
1148
     * List parked calls
1149
     *
1150
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+ParkedCalls
1151
     *
1152
     * @param string|null $actionid   message matching variable
1153
     * @param string|null $parkinglot message matching variable
1154
     *
1155
     * @return array
1156
     */
1157
    public function ParkedCalls(?string $parkinglot = null, ?string $actionid = null): array
1158
    {
1159
        $parameters = [];
1160
        if ($actionid) {
1161
            $parameters['ActionID'] = $actionid;
1162
        }
1163
        if ($parkinglot) {
1164
            $parameters['ParkingLot'] = $parkinglot;
1165
        }
1166
        $result = $this->sendRequestTimeout('ParkedCalls', $parameters);
1167
1168
        return $result;
1169
    }
1170
1171
    /**
1172
     * Queue Add
1173
     *
1174
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueAdd
1175
     *
1176
     * @param string $queue
1177
     * @param string $interface
1178
     * @param int $penalty
1179
     *
1180
     * @return array
1181
     */
1182
    public function QueueAdd(string $queue, string $interface, int $penalty = 0): array
1183
    {
1184
        $parameters = ['Queue' => $queue, 'Interface' => $interface];
1185
        if ($penalty) {
1186
            $parameters['Penalty'] = $penalty;
1187
        }
1188
1189
        return $this->sendRequest('QueueAdd', $parameters);
1190
    }
1191
1192
    /**
1193
     * Queue Remove
1194
     *
1195
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueRemove
1196
     *
1197
     * @param string $queue
1198
     * @param string $interface
1199
     *
1200
     * @return array
1201
     */
1202
    public function QueueRemove(string $queue, string $interface): array
1203
    {
1204
        return $this->sendRequest('QueueRemove', ['Queue' => $queue, 'Interface' => $interface]);
1205
    }
1206
1207
    /**
1208
     * Queues
1209
     *
1210
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Queues
1211
     */
1212
    public function Queues(): array
1213
    {
1214
        return $this->sendRequest('Queues');
1215
    }
1216
1217
    /**
1218
     * Queue Status
1219
     *
1220
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+QueueStatus
1221
     *
1222
     * @param string|null $actionid message matching variable
1223
     *
1224
     * @return array
1225
     */
1226
    public function QueueStatus(?string $actionid = null): array
1227
    {
1228
        if ($actionid) {
1229
            return $this->sendRequest('QueueStatus', ['ActionID' => $actionid]);
1230
        } else {
1231
            return $this->sendRequest('QueueStatus');
1232
        }
1233
    }
1234
1235
    /**
1236
     * Redirect
1237
     *
1238
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Redirect
1239
     *
1240
     * @param string $channel
1241
     * @param string $extrachannel
1242
     * @param string $exten
1243
     * @param string $context
1244
     * @param int $priority
1245
     *
1246
     * @return array
1247
     */
1248
    public function Redirect(string $channel, string $extrachannel, string $exten, string $context, int $priority): array
1249
    {
1250
        return $this->sendRequest(
1251
            'Redirect',
1252
            [
1253
                'Channel'      => $channel,
1254
                'ExtraChannel' => $extrachannel,
1255
                'Exten'        => $exten,
1256
                'Context'      => $context,
1257
                'Priority'     => $priority,
1258
            ]
1259
        );
1260
    }
1261
1262
    /**
1263
     * Set the CDR UserField
1264
     *
1265
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetCDRUserField
1266
     *
1267
     * @param string $userfield
1268
     * @param string $channel
1269
     * @param string|null $append
1270
     *
1271
     * @return array
1272
     */
1273
    public function SetCDRUserField(string $userfield, string $channel, ?string $append = null): array
1274
    {
1275
        $parameters = ['UserField' => $userfield, 'Channel' => $channel];
1276
        if ($append) {
1277
            $parameters['Append'] = $append;
1278
        }
1279
1280
        return $this->sendRequest('SetCDRUserField', $parameters);
1281
    }
1282
1283
    /**
1284
     * Set Channel Variable
1285
     *
1286
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+SetVar
1287
     *
1288
     * @param string $channel  Channel to set variable for
1289
     * @param string $variable name
1290
     * @param string $value
1291
     *
1292
     * @return array
1293
     */
1294
    public function SetVar(string $channel, string $variable, string $value): array
1295
    {
1296
        $params = [
1297
            'Channel'   => $channel,
1298
            'Variable'  => $variable,
1299
            'Value'     => $value
1300
        ];
1301
        return $this->sendRequestTimeout('SetVar', $params);
1302
    }
1303
1304
    /**
1305
     * Channel Status
1306
     *
1307
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+Status
1308
     *
1309
     * @param string $channel
1310
     * @param string|null $actionid message matching variable
1311
     *
1312
     * @return array
1313
     */
1314
    public function Status(string $channel, ?string $actionid = null): array
1315
    {
1316
        $parameters = ['Channel' => $channel];
1317
        if ($actionid) {
1318
            $parameters['ActionID'] = $actionid;
1319
        }
1320
1321
        return $this->sendRequest('Status', $parameters);
1322
    }
1323
1324
    /*
1325
    * MIKO Start.
1326
    */
1327
1328
    /**
1329
     * Stop monitoring a channel
1330
     *
1331
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+StopMonitor
1332
     *
1333
     * @param string $channel
1334
     *
1335
     * @return array
1336
     */
1337
    public function StopMonitor(string $channel): array
1338
    {
1339
        return $this->sendRequest('StopMonitor', ['Channel' => $channel]);
1340
    }
1341
1342
    /**
1343
     * Get the SIP registry information.
1344
     *
1345
     * @return array The SIP registry information.
1346
     */
1347
    public function getSipRegistry(): array
1348
    {
1349
        $peers  = [];
1350
        $result = $this->sendRequestTimeout('SIPshowregistry');
1351
        if ($result['data'] !== null && $result['data']['RegistryEntry'] !== null) {
1352
            foreach ($result['data']['RegistryEntry'] as $peer) {
1353
                $peers[] = [
1354
                    'id'       => $peer['Username'],
1355
                    'state'    => strtoupper($peer['State']),
1356
                    'host'     => $peer['Host'],
1357
                    'username' => $peer['Username'],
1358
                ];
1359
            }
1360
        }
1361
1362
        return $peers;
1363
    }
1364
1365
    /**
1366
     * Get the PJSIP registry information.
1367
     *
1368
     * @return array The PJSIP registry information.
1369
     */
1370
    public function getPjSipRegistry(): array
1371
    {
1372
        $peers  = [];
1373
        $result = $this->sendRequestTimeout('PJSIPShowRegistrationsOutbound');
1374
        if (isset($result['data']['OutboundRegistrationDetail'])) {
1375
            foreach ($result['data']['OutboundRegistrationDetail'] as $peer) {
1376
                [$sip, $host, $port] = explode(':', $peer['ServerUri']);
1377
                $peers[] = [
1378
                    'id'       => str_replace('REG-', '', $peer['ObjectName']),
1379
                    'state'    => strtoupper($peer['Status']),
1380
                    'host'     => $host,
1381
                    'username' => $peer['ContactUser'],
1382
                ];
1383
                unset($sip, $port);
1384
            }
1385
        }
1386
1387
        return $peers;
1388
    }
1389
1390
    /**
1391
     * Get the PJSIP peers information.
1392
     *
1393
     * @return array The PJSIP peers information.
1394
     */
1395
    public function getPjSipPeers(): array
1396
    {
1397
        $peers  = [];
1398
        $result = $this->sendRequestTimeout('PJSIPShowEndpoints');
1399
        $endpoints = $result['data']['EndpointList'] ?? [];
1400
        foreach ($endpoints as $peer) {
1401
            if ($peer['ObjectName'] === 'anonymous') {
1402
                continue;
1403
            }
1404
            $state_array = [
1405
                'Not in use' => 'OK',
1406
                'Busy'       => 'OK',
1407
                'Ringing'    => 'OK'
1408
            ];
1409
            $state       = $state_array[$peer['DeviceState']] ?? 'UNKNOWN';
1410
            $oldAState   = $peers[$peer['Auths']]['state'] ?? '';
1411
1412
            if ('OK' === $oldAState || empty($peer['Auths'])) {
1413
                continue;
1414
            }
1415
1416
            $peers[$peer['Auths']] = [
1417
                'id'             => $peer['Auths'],
1418
                'state'          => strtoupper($state),
1419
                'detailed-state' => $peer['DeviceState']
1420
            ];
1421
        }
1422
        return array_values($peers);
1423
    }
1424
1425
    /**
1426
     * Get the SIP peers information.
1427
     *
1428
     * @return array The SIP peers information.
1429
     */
1430
    public function getSipPeers(): array
1431
    {
1432
        $peers = [];
1433
        $res   = $this->sendRequestTimeout('SIPpeers');
1434
        if (isset($res['data']) && $res['data'] != null && $res['data']['PeerEntry'] != null) {
1435
            foreach ($res['data']['PeerEntry'] as $peer) {
1436
                if (! is_numeric($peer['ObjectName'])) {
1437
                    continue;
1438
                }
1439
                // if ('Unmonitored' == $peer['Status']) continue;
1440
                $arr_status = explode(' ', $peer['Status']);
1441
                $peers[]    = ['id' => $peer['ObjectName'], 'state' => strtoupper($arr_status[0]),];
1442
            }
1443
        }
1444
1445
        return $peers;
1446
    }
1447
1448
    /**
1449
     * Get the SIP peer information.
1450
     *
1451
     * @param string $peer The peer name.
1452
     * @return array The SIP peer information.
1453
     */
1454
    public function getSipPeer(string $peer): array
1455
    {
1456
        $parameters           = ['Peer' => trim($peer)];
1457
        $res                  = $this->sendRequestTimeout('SIPshowpeer', $parameters);
1458
        $arr_status           = explode(' ', $res['Status']);
1459
        $res['state']         = strtoupper($arr_status[0]);
1460
        $res['time-response'] = strtoupper(str_replace(['(', ')'], '', $arr_status[1]));
1461
1462
        return $res;
1463
    }
1464
1465
    /**
1466
     * Get the PJSIP peer information.
1467
     *
1468
     * @param string $peer The peer name.
1469
     * @param string $prefix The prefix for the peer (optional).
1470
     * @return array The PJSIP peer information.
1471
     */
1472
    public function getPjSipPeer(string $peer, string $prefix = ''): array
1473
    {
1474
        $result     = [];
1475
        if (empty($prefix)) {
1476
            $wsResult     = $this->getPjSipPeer($peer, "WS");
1477
            if ($wsResult['state'] !== 'UNKNOWN') {
1478
                $result = $wsResult;
1479
            }
1480
            $parameters = ['Endpoint' => trim($peer)];
1481
            unset($wsResult);
1482
        } else {
1483
            $parameters = ['Endpoint' => trim($peer) . "-$prefix"];
1484
        }
1485
1486
        $res        = $this->sendRequestTimeout('PJSIPShowEndpoint', $parameters);
1487
        $generalRecordFound = !empty($result);
1488
        foreach ($res['data']['ContactStatusDetail'] ?? [] as $index => $data) {
1489
            $suffix = "-$prefix$index";
1490
            if (!empty($data['URI']) && !$generalRecordFound) {
1491
                $generalRecordFound = true;
1492
                $suffix = '';
1493
            }
1494
            foreach ($data as $key => $value) {
1495
                $result["$key$suffix"] = $value;
1496
            }
1497
        }
1498
        $result['state'] = ! empty($result['URI']) ? 'OK' : 'UNKNOWN';
1499
        return $result;
1500
    }
1501
1502
    /*
1503
    * MIKO End.
1504
    */
1505
1506
    // *********************************************************************************************************
1507
    // **                       MISC                                                                          **
1508
    // *********************************************************************************************************
1509
1510
    /**
1511
     * @param  string $conference
1512
     * @param array $vars
1513
     *
1514
     * @return array
1515
     */
1516
    public function meetMeCollectInfo(string $conference, array $vars = []): array
1517
    {
1518
        $result    = [];
1519
        $conf_data = $this->MeetmeList($conference);
1520
        if (! isset($conf_data['data']['MeetmeList'])) {
1521
            return $result;
1522
        }
1523
        foreach ($conf_data['data']['MeetmeList'] as $user_data) {
1524
            $user_data['linkedid']  = $this->GetVar($user_data['Channel'], 'CHANNEL(linkedid)', null, false);
1525
            $user_data['meetme_id'] = $this->GetVar($user_data['Channel'], 'MEETMEUNIQUEID', null, false);
1526
1527
            foreach ($vars as $var) {
1528
                $user_data[$var] = $this->GetVar($user_data['Channel'], $var, null, false);
1529
            }
1530
1531
            $result[] = $user_data;
1532
        }
1533
1534
        return $result;
1535
    }
1536
1537
    /**
1538
     * MeetmeList
1539
     *
1540
     * @param string $Conference
1541
     * @param string $ActionID
1542
     *
1543
     * @return array
1544
     */
1545
    public function MeetmeList(string $Conference, string $ActionID = ''): array
1546
    {
1547
        if (empty($ActionID)) {
1548
            $ActionID = Util::generateRandomString(5);
1549
        }
1550
        $parameters = [
1551
            'Conference' => $Conference,
1552
            'ActionID'   => $ActionID,
1553
        ];
1554
1555
        return $this->sendRequestTimeout('MeetmeList', $parameters);
1556
    }
1557
1558
    /**
1559
     * Gets a Channel Variable
1560
     *
1561
     * @link http://www.voip-info.org/wiki-Asterisk+Manager+API+Action+GetVar
1562
     * @link http://www.voip-info.org/wiki-Asterisk+variables
1563
     *
1564
     * @param string $channel  Channel to read variable from
1565
     * @param string $variable
1566
     * @param string|null $actionId message matching variable
1567
     * @param bool $retArray
1568
     *
1569
     * @return string | array
1570
     */
1571
    public function GetVar(string $channel, string $variable, string $actionId = null, bool $retArray = true): array|string
1572
    {
1573
        $parameters = ['Channel' => $channel, 'Variable' => $variable];
1574
        if ($actionId) {
1575
            $parameters['ActionID'] = $actionId;
1576
        }
1577
1578
        $data = $this->sendRequestTimeout('GetVar', $parameters);
1579
        if ($retArray !== true) {
1580
            $data = (isset($data['Value']) && $data['Value']) ? $data['Value'] : '';
1581
        }
1582
1583
        return $data;
1584
    }
1585
1586
    /**
1587
     * Add event handler
1588
     *
1589
     * Known Events include ( http://www.voip-info.org/wiki-asterisk+manager+events )
1590
     *   Link - Fired when two voice channels are linked together and voice data exchange commences.
1591
     *   Unlink - Fired when a link between two voice channels is discontinued, for example, just before call
1592
     *   completion. Newexten - Hangup - Newchannel - Newstate - Reload - Fired when the "RELOAD" console command is
1593
     *   executed. Shutdown - ExtensionStatus - Rename - Newcallerid - Alarm - AlarmClear - Agentcallbacklogoff -
1594
     *   Agentcallbacklogin - Agentlogoff - MeetmeJoin - MessageWaiting - join - leave - AgentCalled - ParkedCall -
1595
     *   Fired after ParkedCalls Cdr - ParkedCallsComplete - QueueParams - QueueMember - QueueStatusEnd - Status -
1596
     *   StatusComplete - ZapShowChannels - Fired after ZapShowChannels ZapShowChannelsComplete -
1597
     *
1598
     * @param string $event    type or * for default handler
1599
     * @param array | string $callback function
1600
     *
1601
     * @return bool sucess
1602
     */
1603
    public function addEventHandler(string $event, array|string $callback): bool
1604
    {
1605
        $event = strtolower($event);
1606
        if (isset($this->event_handlers[$event])) {
1607
            return false;
1608
        }
1609
        $this->event_handlers[$event] = $callback;
1610
1611
        return true;
1612
    }
1613
}
1614