Passed
Push — master ( 1d0c59...b84937 )
by Nikolay
19:20 queued 13:00
created

AsteriskManager::Originate()   A

Complexity

Conditions 4
Paths 6

Size

Total Lines 37
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

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