AsteriskManager::getPjSipRegistry()   A
last analyzed

Complexity

Conditions 3
Paths 2

Size

Total Lines 18
Code Lines 12

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 12
c 0
b 0
f 0
dl 0
loc 18
rs 9.8666
cc 3
nc 2
nop 0
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