Passed
Push — master ( af4cfe...ae3250 )
by Chizhov
01:31
created

AGI::fastpass_swift()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 3
eloc 7
c 2
b 0
f 0
nc 2
nop 5
dl 0
loc 9
rs 10
1
<?php
2
3
/**
4
 * phpagi.php : PHP AGI Functions for Asterisk
5
 * @see https://github.com/welltime/phpagi
6
 * @filesource http://phpagi.sourceforge.net/
7
 *
8
 * $Id: phpagi.php,v 2.20 2010/09/30 02:21:00 masham Exp $
9
 *
10
 * Copyright (c) 2003 - 2010 Matthew Asham <[email protected]>, David Eder <[email protected]> and others
11
 * All Rights Reserved.
12
 *
13
 * This software is released under the terms of the GNU Lesser General Public License v2.1
14
 * A copy of which is available from http://www.gnu.org/copyleft/lesser.html
15
 *
16
 * We would be happy to list your phpagi based application on the phpagi
17
 * website.  Drop me an Email if you'd like us to list your program.
18
 *
19
 *
20
 * Written for PHP 4.3.4, should work with older PHP 4.x versions.
21
 *
22
 * Please submit bug reports, patches, etc to https://github.com/welltime/phpagi
23
 *
24
 *
25
 * @package phpAGI
26
 * @version 2.20
27
 */
28
29
if (!class_exists('AGI_AsteriskManager')) {
30
  require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi-asmanager.php');
31
}
32
33
define('AST_CONFIG_DIR', '/etc/asterisk/');
34
define('AST_SPOOL_DIR', '/var/spool/asterisk/');
35
define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/');
36
define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf');
37
38
define('AST_DIGIT_ANY', '0123456789#*');
39
40
define('AGIRES_OK', 200);
41
42
define('AST_STATE_DOWN', 0);
43
define('AST_STATE_RESERVED', 1);
44
define('AST_STATE_OFFHOOK', 2);
45
define('AST_STATE_DIALING', 3);
46
define('AST_STATE_RING', 4);
47
define('AST_STATE_RINGING', 5);
48
define('AST_STATE_UP', 6);
49
define('AST_STATE_BUSY', 7);
50
define('AST_STATE_DIALING_OFFHOOK', 8);
51
define('AST_STATE_PRERING', 9);
52
53
define('AUDIO_FILENO', 3); // STDERR_FILENO + 1
54
55
/**
56
 * AGI class
57
 *
58
 * @package phpAGI
59
 * @link http://www.voip-info.org/wiki-Asterisk+agi
60
 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
61
 * @example examples/input.php Get text input from the user and say it back
62
 * @example examples/ping.php Ping an IP address
63
 */
64
class AGI
65
{
66
  /**
67
   * Request variables read in on initialization.
68
   *
69
   * Often contains any/all of the following:
70
   *   agi_request - name of agi script
71
   *   agi_channel - current channel
72
   *   agi_language - current language
73
   *   agi_type - channel type (SIP, ZAP, IAX, ...)
74
   *   agi_uniqueid - unique id based on unix time
75
   *   agi_callerid - callerID string
76
   *   agi_dnid - dialed number id
77
   *   agi_rdnis - referring DNIS number
78
   *   agi_context - current context
79
   *   agi_extension - extension dialed
80
   *   agi_priority - current priority
81
   *   agi_enhanced - value is 1.0 if started as an EAGI script
82
   *   agi_accountcode - set by SetAccount in the dialplan
83
   *   agi_network - value is yes if this is a fastagi
84
   *   agi_network_script - name of the script to execute
85
   *
86
   * NOTE: program arguments are still in $_SERVER['argv'].
87
   *
88
   * @var array
89
   * @access public
90
   */
91
  public $request;
92
93
  /**
94
   * Config variables
95
   *
96
   * @var array
97
   * @access public
98
   */
99
  public $config;
100
101
  /**
102
   * Asterisk Manager
103
   *
104
   * @var AGI_AsteriskManager
105
   * @access public
106
   */
107
  public $asm;
108
109
  /**
110
   * Input Stream
111
   *
112
   * @access private
113
   */
114
  private $in = null;
115
116
  /**
117
   * Output Stream
118
   *
119
   * @access private
120
   */
121
  private $out = null;
122
123
  /**
124
   * Audio Stream
125
   *
126
   * @access public
127
   */
128
  public $audio = null;
129
130
131
  /**
132
   * Application option delimiter
133
   *
134
   * @access public
135
   */
136
  public $option_delim = ",";
137
138
  private $defaultConfig = ["error_handler" => true,
139
                            "debug" => false,
140
                            "admin" => null,
141
                            "tempdir" => AST_TMP_DIR];
142
143
    /**
144
   * Constructor
145
   *
146
   * @param string $config is the name of the config file to parse
147
   * @param array $optconfig is an array of configuration vars and values, stuffed into $this->config['phpagi']
148
   */
149
  function __construct($config = null, array $optconfig = []) {
150
    // load config
151
    if (!is_null($config) && file_exists($config)) {
152
      $this->config = parse_ini_file($config, true);
153
    } elseif (file_exists(DEFAULT_PHPAGI_CONFIG)) {
154
      $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true);
155
    }
156
157
    // If optconfig is specified, stuff values and vars into 'phpagi' config array.
158
    foreach ($optconfig as $var => $val) {
159
      $this->config['phpagi'][$var] = $val;
160
    }
161
162
    // add default values to config for uninitialized values
163
    foreach ($this->defaultConfig as $name => $value) {
164
      $this->config["phpagi"][$name] = $this->config["phpagi"][$name] ?? $value;
165
    }
166
167
    // festival TTS config
168
    $this->config['festival']['text2wave'] = $this->config['festival']['text2wave'] ?? $this->which('text2wave');
169
170
    // swift TTS config
171
    $this->config['cepstral']['swift'] = $this->config['cepstral']['swift'] ?? $this->which('swift');
172
173
    ob_implicit_flush();
174
175
    // open stdin & stdout
176
    $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
177
    $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w');
178
179
    // initialize error handler
180
    if ($this->config['phpagi']['error_handler']) {
181
      set_error_handler('phpagi_error_handler');
182
      global $phpagi_error_handler_email;
183
      $phpagi_error_handler_email = $this->config['phpagi']['admin'];
184
      error_reporting(E_ALL);
185
    }
186
187
    // make sure temp folder exists
188
    $this->make_folder($this->config['phpagi']['tempdir']);
189
190
    // read the request
191
    $str = fgets($this->in);
192
    while ($str != "\n") {
193
      $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1));
194
      $str = fgets($this->in);
195
    }
196
197
    // open audio if eagi detected
198
    if ($this->request['agi_enhanced'] == '1.0') {
199
      if (file_exists('/proc/' . getmypid() . '/fd/3')) {
200
        $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r');
201
      } elseif (file_exists('/dev/fd/3')) {
202
        // may need to mount fdescfs
203
        $this->audio = fopen('/dev/fd/3', 'r');
204
      } else {
205
        $this->conlog('Unable to open audio stream');
206
      }
207
208
      if ($this->audio) {
209
        stream_set_blocking($this->audio, 0);
210
      }
211
    }
212
213
    $this->conlog('AGI Request:');
214
    $this->conlog(print_r($this->request, true));
0 ignored issues
show
Bug introduced by
It seems like print_r($this->request, true) can also be of type true; however, parameter $str of AGI::conlog() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

214
    $this->conlog(/** @scrutinizer ignore-type */ print_r($this->request, true));
Loading history...
215
    $this->conlog('PHPAGI internal configuration:');
216
    $this->conlog(print_r($this->config, true));
217
  }
218
219
  // *********************************************************************************************************
220
  // **                             COMMANDS                                                                                            **
221
  // *********************************************************************************************************
222
223
  /**
224
   * Answer channel if not already in answer state.
225
   *
226
   * @link http://www.voip-info.org/wiki-answer
227
   * @example examples/dtmf.php Get DTMF tones from the user and say the digits
228
   * @example examples/input.php Get text input from the user and say it back
229
   * @example examples/ping.php Ping an IP address
230
   *
231
   * @return array, see evaluate for return information.  ['result'] is 0 on success, -1 on failure.
232
   */
233
  function answer(): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
234
    return $this->evaluate('ANSWER');
235
  }
236
237
  /**
238
   * Get the status of the specified channel. If no channel name is specified, return the status of the current channel.
239
   *
240
   * @link http://www.voip-info.org/wiki-channel+status
241
   * @param string $channel
242
   * @return array, see evaluate for return information. ['data'] contains description.
243
   */
244
  function channel_status(string $channel = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
245
    $ret = $this->evaluate("CHANNEL STATUS $channel");
246
    switch ($ret['result']) {
247
      case -1:
248
        $ret['data'] = trim("There is no channel that matches $channel");
249
        break;
250
      case AST_STATE_DOWN:
251
        $ret['data'] = 'Channel is down and available';
252
        break;
253
      case AST_STATE_RESERVED:
254
        $ret['data'] = 'Channel is down, but reserved';
255
        break;
256
      case AST_STATE_OFFHOOK:
257
        $ret['data'] = 'Channel is off hook';
258
        break;
259
      case AST_STATE_DIALING:
260
        $ret['data'] = 'Digits (or equivalent) have been dialed';
261
        break;
262
      case AST_STATE_RING:
263
        $ret['data'] = 'Line is ringing';
264
        break;
265
      case AST_STATE_RINGING:
266
        $ret['data'] = 'Remote end is ringing';
267
        break;
268
      case AST_STATE_UP:
269
        $ret['data'] = 'Line is up';
270
        break;
271
      case AST_STATE_BUSY:
272
        $ret['data'] = 'Line is busy';
273
        break;
274
      case AST_STATE_DIALING_OFFHOOK:
275
        $ret['data'] = 'Digits (or equivalent) have been dialed while offhook';
276
        break;
277
      case AST_STATE_PRERING:
278
        $ret['data'] = 'Channel has detected an incoming call and is waiting for ring';
279
        break;
280
      default:
281
        $ret['data'] = "Unknown ({$ret['result']})";
282
        break;
283
    }
284
    return $ret;
285
  }
286
287
  /**
288
   * Deletes an entry in the Asterisk database for a given family and key.
289
   *
290
   * @link http://www.voip-info.org/wiki-database+del
291
   * @param string $family
292
   * @param string $key
293
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 otherwise.
294
   */
295
  function database_del(string $family, string $key): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
296
    return $this->evaluate("DATABASE DEL \"$family\" \"$key\"");
297
  }
298
299
  /**
300
   * Deletes a family or specific keytree within a family in the Asterisk database.
301
   *
302
   * @link http://www.voip-info.org/wiki-database+deltree
303
   * @param string $family
304
   * @param string $keytree
305
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 otherwise.
306
   */
307
  function database_deltree(string $family, string $keytree = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
308
    $cmd = "DATABASE DELTREE \"$family\"";
309
    if ($keytree != '') {
310
      $cmd .= " \"$keytree\"";
311
    }
312
    return $this->evaluate($cmd);
313
  }
314
315
  /**
316
   * Retrieves an entry in the Asterisk database for a given family and key.
317
   *
318
   * @link http://www.voip-info.org/wiki-database+get
319
   * @param string $family
320
   * @param string $key
321
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 failure. ['data'] holds the value
322
   */
323
  function database_get(string $family, string $key): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
324
    return $this->evaluate("DATABASE GET \"$family\" \"$key\"");
325
  }
326
327
  /**
328
   * Adds or updates an entry in the Asterisk database for a given family, key, and value.
329
   *
330
   * @param string $family
331
   * @param string $key
332
   * @param string $value
333
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 otherwise
334
   */
335
  function database_put(string $family, string $key, string $value): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
336
    $value = str_replace("\n", '\n', addslashes($value));
337
    return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\"");
338
  }
339
340
341
  /**
342
   * Sets a global variable, using Asterisk 1.6 syntax.
343
   *
344
   * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set
345
   *
346
   * @param string $pVariable
347
   * @param string|int|float $pValue
348
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 otherwise
349
   */
350
  function set_global_var(string $pVariable, $pValue): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
351
    if (is_numeric($pValue)) {
352
      return $this->evaluate("Set({$pVariable}={$pValue},g);");
353
    }
354
    return $this->evaluate("Set({$pVariable}=\"{$pValue}\",g);");
355
  }
356
357
358
  /**
359
   * Sets a variable, using Asterisk 1.6 syntax.
360
   *
361
   * @link http://www.voip-info.org/wiki/view/Asterisk+cmd+Set
362
   *
363
   * @param string $pVariable
364
   * @param string|int|float $pValue
365
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 otherwise
366
   */
367
  function set_var(string $pVariable, $pValue): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
368
    if (is_numeric($pValue)) {
369
      return $this->evaluate("Set({$pVariable}={$pValue});");
370
    }
371
    return $this->evaluate("Set({$pVariable}=\"{$pValue}\");");
372
  }
373
374
375
  /**
376
   * Executes the specified Asterisk application with given options.
377
   *
378
   * @link http://www.voip-info.org/wiki-exec
379
   * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands
380
   * @param string $application
381
   * @param mixed $options
382
   * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application
383
   */
384
  function exec(string $application, $options): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
385
    if (is_array($options)) {
386
      $options = join('|', $options);
387
    }
388
    return $this->evaluate("EXEC $application $options");
389
  }
390
391
  /**
392
   * Plays the given file and receives DTMF data.
393
   *
394
   * This is similar to STREAM FILE, but this command can accept and return many DTMF digits,
395
   * while STREAM FILE returns immediately after the first DTMF digit is detected.
396
   *
397
   * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default.
398
   *
399
   * If the user doesn't press any keys when the message plays, there is $timeout milliseconds
400
   * of silence then the command ends.
401
   *
402
   * The user has the opportunity to press a key at any time during the message or the
403
   * post-message silence. If the user presses a key while the message is playing, the
404
   * message stops playing. When the first key is pressed a timer starts counting for
405
   * $timeout milliseconds. Every time the user presses another key the timer is restarted.
406
   * The command ends when the counter goes to zero or the maximum number of digits is entered,
407
   * whichever happens first.
408
   *
409
   * If you don't specify a time out then a default timeout of 2000 is used following a pressed
410
   * digit. If no digits are pressed then 6 seconds of silence follow the message.
411
   *
412
   * If you don't specify $max_digits then the user can enter as many digits as they want.
413
   *
414
   * Pressing the # key has the same effect as the timer running out: the command ends and
415
   * any previously keyed digits are returned. A side effect of this is that there is no
416
   * way to read a # key using this command.
417
   *
418
   * @param string $filename file to play. Do not include file extension.
419
   * @param integer $timeout milliseconds
420
   * @param integer $max_digits
421
   * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present.
422
   *
423
   * This differs from other commands with return DTMF as numbers representing ASCII characters.
424
   * @example examples/ping.php Ping an IP address
425
   *
426
   * @link http://www.voip-info.org/wiki-get+data
427
   */
428
  function get_data(string $filename, $timeout = null, $max_digits = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
429
    return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits"));
430
  }
431
432
  /**
433
   * Fetch the value of a variable.
434
   *
435
   * Does not work with global variables. Does not work with some variables that are generated by modules.
436
   *
437
   * @link http://www.voip-info.org/wiki-get+variable
438
   * @link http://www.voip-info.org/wiki-Asterisk+variables
439
   * @param string $variable name
440
   * @param boolean $getvalue return the value only
441
   * @return mixed, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value. returns value if $getvalue is TRUE
442
   */
443
  function get_variable(string $variable, bool $getvalue = false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
444
    $res = $this->evaluate("GET VARIABLE $variable");
445
    return $getvalue ? $res['data'] : $res;
446
  }
447
448
449
  /**
450
   * Fetch the value of a full variable.
451
   *
452
   *
453
   * @link http://www.voip-info.org/wiki/view/get+full+variable
454
   * @link http://www.voip-info.org/wiki-Asterisk+variables
455
   * @param string $variable name
456
   * @param string $channel channel
457
   * @param boolean $getvalue return the value only
458
   * @return mixed, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value.  returns value if $getvalue is TRUE
459
   */
460
  function get_fullvariable(string $variable, $channel = false, bool $getvalue = false) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
461
    $req = ($channel == false) ? $variable : $variable . ' ' . $channel;
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $channel of type false|string against false; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
462
    $res = $this->evaluate('GET FULL VARIABLE ' . $req);
463
    return $getvalue ? $res['data'] : $res;
464
465
  }
466
467
  /**
468
   * Hangup the specified channel. If no channel name is given, hang up the current channel.
469
   *
470
   * With power comes responsibility. Hanging up channels other than your own isn't something
471
   * that is done routinely. If you are not sure why you are doing so, then don't.
472
   *
473
   * @link http://www.voip-info.org/wiki-hangup
474
   * @example examples/dtmf.php Get DTMF tones from the user and say the digits
475
   * @example examples/input.php Get text input from the user and say it back
476
   * @example examples/ping.php Ping an IP address
477
   *
478
   * @param string $channel
479
   * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure.
480
   */
481
  function hangup(string $channel = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
482
    return $this->evaluate("HANGUP $channel");
483
  }
484
485
  /**
486
   * Does nothing.
487
   *
488
   * @link http://www.voip-info.org/wiki-noop
489
   * @param string $string
490
   * @return array, see evaluate for return information.
491
   */
492
  function noop(string $string = ""): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
493
    return $this->evaluate("NOOP \"$string\"");
494
  }
495
496
  /**
497
   * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for
498
   * a character to arrive, or infinitely if $timeout is zero.
499
   *
500
   * @link http://www.voip-info.org/wiki-receive+char
501
   * @param integer $timeout milliseconds
502
   * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise
503
   * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII.
504
   */
505
  function receive_char(int $timeout = -1): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
506
    return $this->evaluate("RECEIVE CHAR $timeout");
507
  }
508
509
  /**
510
   * Record sound to a file until an acceptable DTMF digit is received or a specified amount of
511
   * time has passed. Optionally the file BEEP is played before recording begins.
512
   *
513
   * @link http://www.voip-info.org/wiki-record+file
514
   * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds
515
   * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used.
516
   * @param string $escape_digits
517
   * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout.
518
   * @param integer $offset to seek to without exceeding the end of the file.
519
   * @param boolean $beep
520
   * @param integer $silence number of seconds of silence allowed before the function returns despite the
521
   * lack of dtmf digits or reaching timeout.
522
   * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the
523
   * DTMF tone. Use chr() to convert to ASCII.
524
   */
525
  function record_file(string $file, string $format, string $escape_digits = '', int $timeout = -1,
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
526
                       $offset = null, bool $beep = false, $silence = null): array {
527
    $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset");
528
    if ($beep) {
529
      $cmd .= ' BEEP';
530
    }
531
    if (!is_null($silence)) {
532
      $cmd .= " s=$silence";
533
    }
534
    return $this->evaluate($cmd);
535
  }
536
537
  /**
538
   * Say a given character string, returning early if any of the given DTMF digits are received on the channel.
539
   *
540
   * @link https://www.voip-info.org/say-alpha
541
   * @param string $text
542
   * @param string $escape_digits
543
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
544
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
545
   */
546
  function say_alpha(string $text, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
547
    return $this->evaluate("SAY ALPHA $text \"$escape_digits\"");
548
  }
549
550
  /**
551
   * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel.
552
   *
553
   * @link http://www.voip-info.org/wiki-say+digits
554
   * @param integer $digits
555
   * @param string $escape_digits
556
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
557
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
558
   */
559
  function say_digits(int $digits, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
560
    return $this->evaluate("SAY DIGITS $digits \"$escape_digits\"");
561
  }
562
563
  /**
564
   * Say the given number, returning early if any of the given DTMF escape digits are received on the channel.
565
   *
566
   * @link http://www.voip-info.org/wiki-say+number
567
   * @param integer $number
568
   * @param string $escape_digits
569
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
570
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
571
   */
572
  function say_number(int $number, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
573
    return $this->evaluate("SAY NUMBER $number \"$escape_digits\"");
574
  }
575
576
  /**
577
   * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel.
578
   *
579
   * @link http://www.voip-info.org/wiki-say+phonetic
580
   * @param string $text
581
   * @param string $escape_digits
582
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
583
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
584
   */
585
  function say_phonetic(string $text, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
586
    return $this->evaluate("SAY PHONETIC $text \"$escape_digits\"");
587
  }
588
589
  /**
590
   * Say a given time, returning early if any of the given DTMF escape digits are received on the channel.
591
   *
592
   * @link http://www.voip-info.org/wiki-say+time
593
   * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC).
594
   * @param string $escape_digits
595
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
596
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
597
   */
598
  function say_time($time = null, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
599
    if (is_null($time)) {
600
      $time = time();
601
    }
602
    return $this->evaluate("SAY TIME $time \"$escape_digits\"");
603
  }
604
605
  /**
606
   * Send the specified image on a channel.
607
   *
608
   * Most channels do not support the transmission of images.
609
   *
610
   * @link http://www.voip-info.org/wiki-send+image
611
   * @param string $image without extension, often in /var/lib/asterisk/images
612
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or
613
   * channel does not support image transmission.
614
   */
615
  function send_image(string $image): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
616
    return $this->evaluate("SEND IMAGE $image");
617
  }
618
619
  /**
620
   * Send the given text to the connected channel.
621
   *
622
   * Most channels do not support transmission of text.
623
   *
624
   * @link http://www.voip-info.org/wiki-send+text
625
   * @param $text
626
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or
627
   * channel does not support text transmission.
628
   */
629
  function send_text($text): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
630
    return $this->evaluate("SEND TEXT \"$text\"");
631
  }
632
633
  /**
634
   * Cause the channel to automatically hangup at $time seconds in the future.
635
   * If $time is 0 then the auto hangup feature is disabled on this channel.
636
   *
637
   * If the channel is hangup prior to $time seconds, this setting has no effect.
638
   *
639
   * @link http://www.voip-info.org/wiki-set+autohangup
640
   * @param integer $time until automatic hangup
641
   * @return array, see evaluate for return information.
642
   */
643
  function set_autohangup(int $time = 0): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
644
    return $this->evaluate("SET AUTOHANGUP $time");
645
  }
646
647
  /**
648
   * Changes the caller ID of the current channel.
649
   *
650
   * @link http://www.voip-info.org/wiki-set+callerid
651
   * @param string $cid example: "John Smith"<1234567>
652
   * This command will let you take liberties with the <caller ID specification> but the format shown in the example above works
653
   * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then
654
   * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name
655
   * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the
656
   * number will be considered to be part of the name.
657
   * @return array, see evaluate for return information.
658
   */
659
  function set_callerid(string $cid): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
660
    return $this->evaluate("SET CALLERID $cid");
661
  }
662
663
  /**
664
   * Sets the context for continuation upon exiting the application.
665
   *
666
   * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new
667
   * context you should set extension and priority yourself.
668
   *
669
   * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a
670
   * warning message on the Asterisk console.
671
   *
672
   * @link http://www.voip-info.org/wiki-set+context
673
   * @param string $context
674
   * @return array, see evaluate for return information.
675
   */
676
  function set_context(string $context): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
677
    return $this->evaluate("SET CONTEXT $context");
678
  }
679
680
  /**
681
   * Set the extension to be used for continuation upon exiting the application.
682
   *
683
   * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the
684
   * extension you should set the priority yourself.
685
   *
686
   * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do
687
   * get a warning message on the Asterisk console.
688
   *
689
   * @link http://www.voip-info.org/wiki-set+extension
690
   * @param string $extension
691
   * @return array, see evaluate for return information.
692
   */
693
  function set_extension(string $extension): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
694
    return $this->evaluate("SET EXTENSION $extension");
695
  }
696
697
  /**
698
   * Enable/Disable Music on hold generator.
699
   *
700
   * @link http://www.voip-info.org/wiki-set+music
701
   * @param boolean $enabled
702
   * @param string $class
703
   * @return array, see evaluate for return information.
704
   */
705
  function set_music(bool $enabled = true, string $class = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
706
    $enabled = ($enabled) ? 'ON' : 'OFF';
707
    return $this->evaluate("SET MUSIC $enabled $class");
708
  }
709
710
  /**
711
   * Set the priority to be used for continuation upon exiting the application.
712
   *
713
   * If you specify a non-existent priority you receive no error indication (['result'] is still 0)
714
   * and no warning is issued on the Asterisk console.
715
   *
716
   * @link http://www.voip-info.org/wiki-set+priority
717
   * @param integer $priority
718
   * @return array, see evaluate for return information.
719
   */
720
  function set_priority(int $priority): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
721
    return $this->evaluate("SET PRIORITY $priority");
722
  }
723
724
  /**
725
   * Sets a variable to the specified value. The variables so created can later be used by later using ${<variablename>}
726
   * in the dialplan.
727
   *
728
   * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary.
729
   * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted
730
   * and any variables in that channel are deleted as well.
731
   *
732
   * @link http://www.voip-info.org/wiki-set+variable
733
   * @param string $variable is case sensitive
734
   * @param string $value
735
   * @return array, see evaluate for return information.
736
   */
737
  function set_variable(string $variable, string $value): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
738
    $value = str_replace("\n", '\n', addslashes($value));
739
    return $this->evaluate("SET VARIABLE $variable \"$value\"");
740
  }
741
742
  /**
743
   * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA
744
   * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of
745
   * digits before returning.
746
   *
747
   * @param string $filename without extension, often in /var/lib/asterisk/sounds
748
   * @param string $escape_digits
749
   * @param integer $offset
750
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
751
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
752
   * @example examples/ping.php Ping an IP address
753
   *
754
   * @link http://www.voip-info.org/wiki-stream+file
755
   */
756
  function stream_file(string $filename, string $escape_digits = '', int $offset = 0): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
757
    return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset");
758
  }
759
760
  /**
761
   * Enable or disable TDD transmission/reception on the current channel.
762
   *
763
   * @link http://www.voip-info.org/wiki-tdd+mode
764
   * @param string $setting can be on, off or mate
765
   * @return array, see evaluate for return information. ['result'] is 1 on success, 0 if the channel is not TDD capable.
766
   */
767
  function tdd_mode(string $setting): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
768
    return $this->evaluate("TDD MODE $setting");
769
  }
770
771
  /**
772
   * Sends $message to the Asterisk console via the 'verbose' message system.
773
   *
774
   * If the Asterisk verbosity level is $level or greater, send $message to the console.
775
   *
776
   * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later
777
   * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal
778
   * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages
779
   * should have a high verbose level.
780
   *
781
   * @link http://www.voip-info.org/wiki-verbose
782
   * @param string $message
783
   * @param integer $level from 1 to 4
784
   * @return array, see evaluate for return information.
785
   */
786
  function verbose(string $message, int $level = 1): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
787
    $ret = [];
788
    foreach (explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg) {
789
      @syslog(LOG_WARNING, $msg);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for syslog(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

789
      /** @scrutinizer ignore-unhandled */ @syslog(LOG_WARNING, $msg);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
790
      $ret = $this->evaluate("VERBOSE \"$msg\" $level");
791
    }
792
    return $ret;
793
  }
794
795
  /**
796
   * Waits up to $timeout milliseconds for channel to receive a DTMF digit.
797
   *
798
   * @link http://www.voip-info.org/wiki-wait+for+digit
799
   * @param integer $timeout in milliseconds. Use -1 for the timeout value if you want the call to wait indefinitely.
800
   * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no
801
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
802
   */
803
  function wait_for_digit(int $timeout = -1): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
804
    return $this->evaluate("WAIT FOR DIGIT $timeout");
805
  }
806
807
808
  // *********************************************************************************************************
809
  // **                             APPLICATIONS                                                                                        **
810
  // *********************************************************************************************************
811
812
  /**
813
   * Set absolute maximum time of call.
814
   *
815
   * Note that the timeout is set from the current time forward, not counting the number of seconds the call has already been up.
816
   * Each time you call AbsoluteTimeout(), all previous absolute timeouts are cancelled.
817
   * Will return the call to the T extension so that you can playback an explanatory note to the calling party (the called party
818
   * will not hear that)
819
   *
820
   * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands
821
   * @link http://www.dynx.net/ASTERISK/AGI/ccard/agi-ccard.agi
822
   * @param integer $seconds allowed, 0 disables timeout
823
   * @return array, see evaluate for return information.
824
   */
825
  function exec_absolutetimeout(int $seconds = 0): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
826
    return $this->exec('AbsoluteTimeout', $seconds);
827
  }
828
829
  /**
830
   * Executes an AGI compliant application.
831
   *
832
   * @param string $command
833
   * @param string $args
834
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit.
835
   */
836
  function exec_agi(string $command, string $args): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
837
    return $this->exec("AGI $command", $args);
838
  }
839
840
  /**
841
   * Set Language.
842
   *
843
   * @param string $language code
844
   * @return array, see evaluate for return information.
845
   */
846
  function exec_setlanguage(string $language = 'en'): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
847
    return $this->exec('Set', 'CHANNEL(language)=' . $language);
848
  }
849
850
  /**
851
   * Do ENUM Lookup.
852
   *
853
   * Note: to retrieve the result, use
854
   *   get_variable('ENUM');
855
   *
856
   * @param $exten
857
   * @return array, see evaluate for return information.
858
   */
859
  function exec_enumlookup($exten): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
860
    return $this->exec('EnumLookup', $exten);
861
  }
862
863
  /**
864
   * Dial.
865
   *
866
   * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960
867
   * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones
868
   * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage.
869
   * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt.
870
   *
871
   * @link http://www.voip-info.org/wiki-Asterisk+cmd+Dial
872
   * @param string $type
873
   * @param string $identifier
874
   * @param integer $timeout
875
   * @param string $options
876
   * @param string $url
877
   * @return array, see evaluate for return information.
878
   */
879
  function exec_dial(string $type, string $identifier, $timeout = null, $options = null, $url = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
880
    return $this->exec(
881
      'Dial', trim("$type/$identifier" . $this->option_delim . $timeout . $this->option_delim . $options . $this->option_delim . $url, $this->option_delim));
882
  }
883
884
  /**
885
   * Goto.
886
   *
887
   * This function takes three arguments: context,extension, and priority, but the leading arguments
888
   * are optional, not the trailing arguments.  Those goto($z) sets the priority to $z.
889
   *
890
   * @param string $a
891
   * @param string $b ;
892
   * @param string $c ;
893
   * @return array, see evaluate for return information.
894
   */
895
  function exec_goto(string $a, $b = null, $c = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
896
    return $this->exec('Goto', trim($a . $this->option_delim . $b . $this->option_delim . $c, $this->option_delim));
897
  }
898
899
900
  // *********************************************************************************************************
901
  // **                             FAST PASSING                                                                                        **
902
  // *********************************************************************************************************
903
904
  /**
905
   * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel.
906
   * Return early if $buffer is adequate for request.
907
   *
908
   * @link http://www.voip-info.org/wiki-say+digits
909
   * @param string $buffer
910
   * @param integer $digits
911
   * @param string $escape_digits
912
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
913
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
914
   */
915
  function fastpass_say_digits(string &$buffer, int $digits, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
916
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
917
      $res = $this->say_digits($digits, $escape_digits);
918
      $this->appendBuffer($buffer, $res);
919
      return $res;
920
    }
921
    return ['code' => AGIRES_OK,
922
            'result' => $this->ordBuffer($buffer)];
923
  }
924
925
  /**
926
   * Say the given number, returning early if any of the given DTMF escape digits are received on the channel.
927
   * Return early if $buffer is adequate for request.
928
   *
929
   * @link http://www.voip-info.org/wiki-say+number
930
   * @param string $buffer
931
   * @param integer $number
932
   * @param string $escape_digits
933
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
934
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
935
   */
936
  function fastpass_say_number(string &$buffer, int $number, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
937
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
938
      $res = $this->say_number($number, $escape_digits);
939
      $this->appendBuffer($buffer, $res);
940
      return $res;
941
    }
942
    return ['code' => AGIRES_OK,
943
            'result' => $this->ordBuffer($buffer)];
944
  }
945
946
  /**
947
   * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel.
948
   * Return early if $buffer is adequate for request.
949
   *
950
   * @link http://www.voip-info.org/wiki-say+phonetic
951
   * @param string $buffer
952
   * @param string $text
953
   * @param string $escape_digits
954
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
955
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
956
   */
957
  function fastpass_say_phonetic(string &$buffer, string $text, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
958
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
959
      $res = $this->say_phonetic($text, $escape_digits);
960
      $this->appendBuffer($buffer, $res);
961
      return $res;
962
    }
963
    return ['code' => AGIRES_OK,
964
            'result' => $this->ordBuffer($buffer)];
965
  }
966
967
  /**
968
   * Say a given time, returning early if any of the given DTMF escape digits are received on the channel.
969
   * Return early if $buffer is adequate for request.
970
   *
971
   * @link http://www.voip-info.org/wiki-say+time
972
   * @param string $buffer
973
   * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC).
974
   * @param string $escape_digits
975
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
976
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
977
   */
978
  function fastpass_say_time(string &$buffer, $time = null, string $escape_digits = ''): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
979
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
980
      $res = $this->say_time($time, $escape_digits);
981
      $this->appendBuffer($buffer, $res);
982
      return $res;
983
    }
984
    return ['code' => AGIRES_OK,
985
            'result' => $this->ordBuffer($buffer)];
986
  }
987
988
  /**
989
   * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA
990
   * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of
991
   * digits before returning.
992
   * Return early if $buffer is adequate for request.
993
   *
994
   * @link http://www.voip-info.org/wiki-stream+file
995
   * @param string $buffer
996
   * @param string $filename without extension, often in /var/lib/asterisk/sounds
997
   * @param string $escape_digits
998
   * @param integer $offset
999
   * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
1000
   * digit received, otherwise a decimal value of the DTMF tone.  Use chr() to convert to ASCII.
1001
   */
1002
  function fastpass_stream_file(string &$buffer, string $filename, string $escape_digits = '', int $offset = 0): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1003
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
1004
      $res = $this->stream_file($filename, $escape_digits, $offset);
1005
      $this->appendBuffer($buffer, $res);
1006
      return $res;
1007
    }
1008
    return ['code' => AGIRES_OK,
1009
            'result' => $this->ordBuffer($buffer),
1010
            'endpos' => 0];
1011
  }
1012
1013
  /**
1014
   * Use festival to read text.
1015
   * Return early if $buffer is adequate for request.
1016
   *
1017
   * @link http://www.cstr.ed.ac.uk/projects/festival/
1018
   * @param string $buffer
1019
   * @param string $text
1020
   * @param string $escape_digits
1021
   * @param integer $frequency
1022
   * @return array, see evaluate for return information.
1023
   */
1024
  function fastpass_text2wav(string &$buffer, string $text, string $escape_digits = '', int $frequency = 8000): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1025
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
1026
      $res = $this->text2wav($text, $escape_digits, $frequency);
1027
      $this->appendBuffer($buffer, $res);
1028
      return $res;
1029
    }
1030
    return ['code' => AGIRES_OK,
1031
            'result' => $this->ordBuffer($buffer),
1032
            'endpos' => 0];
1033
  }
1034
1035
  /**
1036
   * Use Cepstral Swift to read text.
1037
   * Return early if $buffer is adequate for request.
1038
   *
1039
   * @link http://www.cepstral.com/
1040
   * @param string $buffer
1041
   * @param string $text
1042
   * @param string $escape_digits
1043
   * @param integer $frequency
1044
   * @param $voice
1045
   * @return array, see evaluate for return information.
1046
   */
1047
  function fastpass_swift(string &$buffer, string $text, string $escape_digits = '', int $frequency = 8000, $voice = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1048
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
1049
      $res = $this->swift($text, $escape_digits, $frequency, $voice);
1050
      $this->appendBuffer($buffer, $res);
1051
      return $res;
1052
    }
1053
    return ['code' => AGIRES_OK,
1054
            'result' => $this->ordBuffer($buffer),
1055
            'endpos' => 0];
1056
  }
1057
1058
  /**
1059
   * Say Punctuation in a string.
1060
   * Return early if $buffer is adequate for request.
1061
   *
1062
   * @param string $buffer
1063
   * @param string $text
1064
   * @param string $escape_digits
1065
   * @param integer $frequency
1066
   * @return array, see evaluate for return information.
1067
   */
1068
  function fastpass_say_punctuation(string &$buffer, string $text, string $escape_digits = '', int $frequency = 8000): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1069
    if ($buffer == '' || $this->proceedDigits($buffer, $escape_digits)) {
1070
      $res = $this->say_punctuation($text, $escape_digits, $frequency);
1071
      $this->appendBuffer($buffer, $res);
1072
      return $res;
1073
    }
1074
    return ['code' => AGIRES_OK,
1075
            'result' => $this->ordBuffer($buffer)];
1076
  }
1077
1078
  /**
1079
   * Plays the given file and receives DTMF data.
1080
   * Return early if $buffer is adequate for request.
1081
   *
1082
   * This is similar to STREAM FILE, but this command can accept and return many DTMF digits,
1083
   * while STREAM FILE returns immediately after the first DTMF digit is detected.
1084
   *
1085
   * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default.
1086
   *
1087
   * If the user doesn't press any keys when the message plays, there is $timeout milliseconds
1088
   * of silence then the command ends.
1089
   *
1090
   * The user has the opportunity to press a key at any time during the message or the
1091
   * post-message silence. If the user presses a key while the message is playing, the
1092
   * message stops playing. When the first key is pressed a timer starts counting for
1093
   * $timeout milliseconds. Every time the user presses another key the timer is restarted.
1094
   * The command ends when the counter goes to zero or the maximum number of digits is entered,
1095
   * whichever happens first.
1096
   *
1097
   * If you don't specify a time out then a default timeout of 2000 is used following a pressed
1098
   * digit. If no digits are pressed then 6 seconds of silence follow the message.
1099
   *
1100
   * If you don't specify $max_digits then the user can enter as many digits as they want.
1101
   *
1102
   * Pressing the # key has the same effect as the timer running out: the command ends and
1103
   * any previously keyed digits are returned. A side effect of this is that there is no
1104
   * way to read a # key using this command.
1105
   *
1106
   * @link http://www.voip-info.org/wiki-get+data
1107
   * @param string $buffer
1108
   * @param string $filename file to play. Do not include file extension.
1109
   * @param integer $timeout milliseconds
1110
   * @param integer $max_digits
1111
   * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present.
1112
   *
1113
   * This differs from other commands with return DTMF as numbers representing ASCII characters.
1114
   */
1115
  function fastpass_get_data(string &$buffer, string $filename, $timeout = null, $max_digits = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1116
    if (is_null($max_digits) || strlen($buffer) < $max_digits) {
1117
      if ($buffer == '') {
1118
        $res = $this->get_data($filename, $timeout, $max_digits);
1119
        if ($res['code'] == AGIRES_OK) {
1120
          $buffer .= $res['result'];
1121
        }
1122
        return $res;
1123
      }
1124
      while (is_null($max_digits) || strlen($buffer) < $max_digits) {
1125
        $res = $this->wait_for_digit();
1126
        if ($res['code'] != AGIRES_OK) {
1127
          return $res;
1128
        }
1129
        if ($res['result'] == ord('#')) {
1130
          break;
1131
        }
1132
        $buffer .= chr($res['result']);
1133
      }
1134
    }
1135
    return ['code' => AGIRES_OK,
1136
            'result' => $buffer];
1137
  }
1138
1139
  // *********************************************************************************************************
1140
  // **                             DERIVED                                                                                             **
1141
  // *********************************************************************************************************
1142
1143
  /**
1144
   * Menu.
1145
   *
1146
   * This function presents the user with a menu and reads the response
1147
   *
1148
   * @param array $choices has the following structure:
1149
   *   array('1'=>'*Press 1 for this', // festival reads if prompt starts with *
1150
   *           '2'=>'some-gsm-without-extension',
1151
   *           '*'=>'*Press star for help');
1152
   * @param int $timeout
1153
   * @return mixed key pressed on success, -1 on failure
1154
   */
1155
  function menu(array $choices, int $timeout = 2000) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1156
    $keys = join('', array_keys($choices));
1157
    $choice = null;
1158
    while (is_null($choice)) {
1159
      foreach ($choices as $prompt) {
1160
        if ($prompt[0] == '*') {
1161
          $ret = $this->text2wav(substr($prompt, 1), $keys);
1162
        } else {
1163
          $ret = $this->stream_file($prompt, $keys);
1164
        }
1165
1166
        if ($ret['code'] != AGIRES_OK || $ret['result'] == -1) {
1167
          $choice = -1;
1168
          break;
1169
        }
1170
1171
        if ($ret['result'] != 0) {
1172
          $choice = chr($ret['result']);
1173
          break;
1174
        }
1175
      }
1176
1177
      if (is_null($choice)) {
1178
        $ret = $this->get_data('beep', $timeout, 1);
1179
        if ($ret['code'] != AGIRES_OK || $ret['result'] == -1) {
1180
          $choice = -1;
1181
        } elseif ($ret['result'] != '' && strpos(' ' . $keys, $ret['result'])) {
1182
          $choice = $ret['result'];
1183
        }
1184
      }
1185
    }
1186
    return $choice;
1187
  }
1188
1189
  /**
1190
   * setContext - Set context, extension and priority.
1191
   *
1192
   * @param string $context
1193
   * @param string $extension
1194
   * @param string $priority
1195
   */
1196
  function setContext(string $context, string $extension = 's', $priority = 1) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1197
    $this->set_context($context);
1198
    $this->set_extension($extension);
1199
    $this->set_priority($priority);
0 ignored issues
show
Bug introduced by
It seems like $priority can also be of type string; however, parameter $priority of AGI::set_priority() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1199
    $this->set_priority(/** @scrutinizer ignore-type */ $priority);
Loading history...
1200
  }
1201
1202
  /**
1203
   * Parse caller id.
1204
   *
1205
   * @param string $callerid
1206
   * @return array('Name'=>$name, 'Number'=>$number)
1207
   * @example examples/dtmf.php Get DTMF tones from the user and say the digits
1208
   * @example examples/input.php Get text input from the user and say it back
1209
   *
1210
   * "name" <proto:user@server:port>
1211
   *
1212
   */
1213
  function parse_callerid($callerid = null): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1214
    if (is_null($callerid)) {
1215
      $callerid = $this->request['agi_callerid'];
1216
    }
1217
1218
    $ret = ['name' => '',
1219
            'protocol' => '',
1220
            'username' => '',
1221
            'host' => '',
1222
            'port' => ''];
1223
    $callerid = trim($callerid);
1224
1225
    if ($callerid[0] == '"' || $callerid[0] == "'") {
1226
      $d = $callerid[0];
1227
      $callerid = explode($d, substr($callerid, 1));
1228
      $ret['name'] = array_shift($callerid);
1229
      $callerid = join($d, $callerid);
1230
    }
1231
1232
    $callerid = explode('@', trim($callerid, '<> '));
1233
    $username = explode(':', array_shift($callerid));
1234
    if (count($username) == 1) {
1235
      $ret['username'] = $username[0];
1236
    } else {
1237
      $ret['protocol'] = array_shift($username);
1238
      $ret['username'] = join(':', $username);
1239
    }
1240
1241
    $callerid = join('@', $callerid);
1242
    $host = explode(':', $callerid);
1243
    if (count($host) == 1) {
1244
      $ret['host'] = $host[0];
1245
    } else {
1246
      $ret['host'] = array_shift($host);
1247
      $ret['port'] = join(':', $host);
1248
    }
1249
1250
    return $ret;
1251
  }
1252
1253
  /**
1254
   * Use festival to read text.
1255
   *
1256
   * @param string $text
1257
   * @param string $escape_digits
1258
   * @param integer $frequency
1259
   * @return array | bool, see evaluate for return information.
1260
   * @example examples/dtmf.php Get DTMF tones from the user and say the digits
1261
   * @example examples/input.php Get text input from the user and say it back
1262
   * @example examples/ping.php Ping an IP address
1263
   *
1264
   * @link http://www.cstr.ed.ac.uk/projects/festival/
1265
   */
1266
  function text2wav(string $text, string $escape_digits = '', int $frequency = 8000) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1267
    $text = trim($text);
1268
    if ($text == '') {
1269
      return true;
1270
    }
1271
1272
    $hash = md5($text);
1273
    $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR;
1274
    $fname .= 'text2wav_' . $hash;
1275
1276
    // create wave file
1277
    if (!file_exists("$fname.wav")) {
1278
      // write text file
1279
      if (!file_exists("$fname.txt")) {
1280
        $fp = fopen("$fname.txt", 'w');
1281
        fputs($fp, $text);
1282
        fclose($fp);
1283
      }
1284
1285
      shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt");
1286
    } else {
1287
      touch("$fname.txt");
1288
      touch("$fname.wav");
1289
    }
1290
1291
    // stream it
1292
    $ret = $this->stream_file($fname, $escape_digits);
1293
1294
    // clean up old files
1295
    $delete = time() - 2592000; // 1 month
1296
    foreach (glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file)
1297
      if (filemtime($file) < $delete) {
1298
        unlink($file);
1299
      }
1300
1301
    return $ret;
1302
  }
1303
1304
  /**
1305
   * Use Cepstral Swift to read text.
1306
   *
1307
   * @link http://www.cepstral.com/
1308
   * @param string $text
1309
   * @param string $escape_digits
1310
   * @param integer $frequency
1311
   * @param $voice
1312
   * @return array | bool, see evaluate for return information.
1313
   */
1314
  function swift(string $text, string $escape_digits = '', int $frequency = 8000, $voice = null) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1315
    if (!is_null($voice)) {
1316
      $voice = "-n $voice";
1317
    } elseif (isset($this->config['cepstral']['voice'])) {
1318
      $voice = "-n {$this->config['cepstral']['voice']}";
1319
    }
1320
1321
    $text = trim($text);
1322
    if ($text == '') {
1323
      return true;
1324
    }
1325
1326
    $hash = md5($text);
1327
    $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR;
1328
    $fname .= 'swift_' . $hash;
1329
1330
    // create wave file
1331
    if (!file_exists("$fname.wav")) {
1332
      // write text file
1333
      if (!file_exists("$fname.txt")) {
1334
        $fp = fopen("$fname.txt", 'w');
1335
        fputs($fp, $text);
1336
        fclose($fp);
1337
      }
1338
1339
      shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt");
1340
    }
1341
1342
    // stream it
1343
    $ret = $this->stream_file($fname, $escape_digits);
1344
1345
    // clean up old files
1346
    $delete = time() - 2592000; // 1 month
1347
    foreach (glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file)
1348
      if (filemtime($file) < $delete) {
1349
        unlink($file);
1350
      }
1351
1352
    return $ret;
1353
  }
1354
1355
  /**
1356
   * Text Input.
1357
   *
1358
   * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText
1359
   *
1360
   * Example:
1361
   *                  UC   H     LC   i        ,     SP   h     o        w    SP   a    r        e     SP   y        o        u     ?
1362
   *   $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*';
1363
   *
1364
   * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText
1365
   * @example examples/input.php Get text input from the user and say it back
1366
   *
1367
   * @param string $mode
1368
   * @return string
1369
   */
1370
  function text_input(string $mode = 'NUMERIC'): string {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1371
    $alpha = ['k0' => ' ',
1372
              'k00' => ',',
1373
              'k000' => '.',
1374
              'k0000' => '?',
1375
              'k00000' => '0',
1376
              'k1' => '!',
1377
              'k11' => ':',
1378
              'k111' => ';',
1379
              'k1111' => '#',
1380
              'k11111' => '1',
1381
              'k2' => 'A',
1382
              'k22' => 'B',
1383
              'k222' => 'C',
1384
              'k2222' => '2',
1385
              'k3' => 'D',
1386
              'k33' => 'E',
1387
              'k333' => 'F',
1388
              'k3333' => '3',
1389
              'k4' => 'G',
1390
              'k44' => 'H',
1391
              'k444' => 'I',
1392
              'k4444' => '4',
1393
              'k5' => 'J',
1394
              'k55' => 'K',
1395
              'k555' => 'L',
1396
              'k5555' => '5',
1397
              'k6' => 'M',
1398
              'k66' => 'N',
1399
              'k666' => 'O',
1400
              'k6666' => '6',
1401
              'k7' => 'P',
1402
              'k77' => 'Q',
1403
              'k777' => 'R',
1404
              'k7777' => 'S',
1405
              'k77777' => '7',
1406
              'k8' => 'T',
1407
              'k88' => 'U',
1408
              'k888' => 'V',
1409
              'k8888' => '8',
1410
              'k9' => 'W',
1411
              'k99' => 'X',
1412
              'k999' => 'Y',
1413
              'k9999' => 'Z',
1414
              'k99999' => '9'];
1415
    $symbol = ['k0' => '=',
1416
               'k1' => '<',
1417
               'k11' => '(',
1418
               'k111' => '[',
1419
               'k1111' => '{',
1420
               'k11111' => '1',
1421
               'k2' => '@',
1422
               'k22' => '$',
1423
               'k222' => '&',
1424
               'k2222' => '%',
1425
               'k22222' => '2',
1426
               'k3' => '>',
1427
               'k33' => ')',
1428
               'k333' => ']',
1429
               'k3333' => '}',
1430
               'k33333' => '3',
1431
               'k4' => '+',
1432
               'k44' => '-',
1433
               'k444' => '*',
1434
               'k4444' => '/',
1435
               'k44444' => '4',
1436
               'k5' => "'",
1437
               'k55' => '`',
1438
               'k555' => '5',
1439
               'k6' => '"',
1440
               'k66' => '6',
1441
               'k7' => '^',
1442
               'k77' => '7',
1443
               'k8' => "\\",
1444
               'k88' => '|',
1445
               'k888' => '8',
1446
               'k9' => '_',
1447
               'k99' => '~',
1448
               'k999' => '9'];
1449
    $text = '';
1450
    do {
1451
      $command = false;
1452
      $result = $this->get_data('beep');
1453
      foreach (explode('*', $result['result']) as $code) {
1454
        if ($command) {
1455
          switch ($code[0]) {
1456
            case '2':
1457
              $text = substr($text, 0, strlen($text) - 1);
1458
              break; // backspace
1459
            case '5':
1460
              $mode = 'LOWERCASE';
1461
              break;
1462
            case '6':
1463
              $mode = 'NUMERIC';
1464
              break;
1465
            case '7':
1466
              $mode = 'SYMBOL';
1467
              break;
1468
            case '8':
1469
              $mode = 'UPPERCASE';
1470
              break;
1471
            case '9':
1472
              $text = explode(' ', $text);
1473
              unset($text[count($text) - 1]);
1474
              $text = join(' ', $text);
1475
              break; // backspace a word
1476
          }
1477
          $code = substr($code, 1);
1478
          $command = false;
1479
        }
1480
        if ($code == '') {
1481
          $command = true;
1482
        } elseif ($mode == 'NUMERIC') {
1483
          $text .= $code;
1484
        } elseif ($mode == 'UPPERCASE' && isset($alpha['k' . $code])) {
1485
          $text .= $alpha['k' . $code];
1486
        } elseif ($mode == 'LOWERCASE' && isset($alpha['k' . $code])) {
1487
          $text .= strtolower($alpha['k' . $code]);
1488
        } elseif ($mode == 'SYMBOL' && isset($symbol['k' . $code])) {
1489
          $text .= $symbol['k' . $code];
1490
        }
1491
      }
1492
      $this->say_punctuation($text);
1493
    } while (substr($result['result'], -2) == '**');
1494
    return $text;
1495
  }
1496
1497
  /**
1498
   * Say Punctuation in a string.
1499
   *
1500
   * @param string $text
1501
   * @param string $escape_digits
1502
   * @param integer $frequency
1503
   * @return array, see evaluate for return information.
1504
   */
1505
  function say_punctuation(string $text, string $escape_digits = '', int $frequency = 8000): array {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1506
    $ret = "";
1507
    for ($i = 0; $i < strlen($text); $i++) {
1508
      switch ($text[$i]) {
1509
        case ' ':
1510
          $ret .= 'SPACE ';
1511
          break;
1512
        case ',':
1513
          $ret .= 'COMMA ';
1514
          break;
1515
        case '.':
1516
          $ret .= 'PERIOD ';
1517
          break;
1518
        case '?':
1519
          $ret .= 'QUESTION MARK ';
1520
          break;
1521
        case '!':
1522
          $ret .= 'EXPLANATION POINT ';
1523
          break;
1524
        case ':':
1525
          $ret .= 'COLON ';
1526
          break;
1527
        case ';':
1528
          $ret .= 'SEMICOLON ';
1529
          break;
1530
        case '#':
1531
          $ret .= 'POUND ';
1532
          break;
1533
        case '=':
1534
          $ret .= 'EQUALS ';
1535
          break;
1536
        case '<':
1537
          $ret .= 'LESS THAN ';
1538
          break;
1539
        case '(':
1540
          $ret .= 'LEFT PARENTHESIS ';
1541
          break;
1542
        case '[':
1543
          $ret .= 'LEFT BRACKET ';
1544
          break;
1545
        case '{':
1546
          $ret .= 'LEFT BRACE ';
1547
          break;
1548
        case '@':
1549
          $ret .= 'AT ';
1550
          break;
1551
        case '$':
1552
          $ret .= 'DOLLAR SIGN ';
1553
          break;
1554
        case '&':
1555
          $ret .= 'AMPERSAND ';
1556
          break;
1557
        case '%':
1558
          $ret .= 'PERCENT ';
1559
          break;
1560
        case '>':
1561
          $ret .= 'GREATER THAN ';
1562
          break;
1563
        case ')':
1564
          $ret .= 'RIGHT PARENTHESIS ';
1565
          break;
1566
        case ']':
1567
          $ret .= 'RIGHT BRACKET ';
1568
          break;
1569
        case '}':
1570
          $ret .= 'RIGHT BRACE ';
1571
          break;
1572
        case '+':
1573
          $ret .= 'PLUS ';
1574
          break;
1575
        case '-':
1576
          $ret .= 'MINUS ';
1577
          break;
1578
        case '*':
1579
          $ret .= 'ASTERISK ';
1580
          break;
1581
        case '/':
1582
          $ret .= 'SLASH ';
1583
          break;
1584
        case "'":
1585
          $ret .= 'SINGLE QUOTE ';
1586
          break;
1587
        case '`':
1588
          $ret .= 'BACK TICK ';
1589
          break;
1590
        case '"':
1591
          $ret .= 'QUOTE ';
1592
          break;
1593
        case '^':
1594
          $ret .= 'CAROT ';
1595
          break;
1596
        case "\\":
1597
          $ret .= 'BACK SLASH ';
1598
          break;
1599
        case '|':
1600
          $ret .= 'BAR ';
1601
          break;
1602
        case '_':
1603
          $ret .= 'UNDERSCORE ';
1604
          break;
1605
        case '~':
1606
          $ret .= 'TILDE ';
1607
          break;
1608
        default:
1609
          $ret .= $text[$i] . ' ';
1610
          break;
1611
      }
1612
    }
1613
    return $this->text2wav($ret, $escape_digits, $frequency);
1614
  }
1615
1616
  /**
1617
   * Create a new AGI_AsteriskManager.
1618
   */
1619
  function &new_AsteriskManager(): AGI_AsteriskManager {
1620
    $this->asm = new AGI_AsteriskManager(null, $this->config['asmanager']);
1621
    $this->asm->setPagi($this);
1622
    $this->config['asmanager'] =& $this->asm->config['asmanager'];
1623
    return $this->asm;
1624
  }
1625
1626
1627
  // *********************************************************************************************************
1628
  // **                             PRIVATE                                                                                             **
1629
  // *********************************************************************************************************
1630
1631
1632
  /**
1633
   * Evaluate an AGI command.
1634
   *
1635
   * @access private
1636
   * @param string $command
1637
   * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data)
1638
   */
1639
  private function evaluate(string $command): array {
1640
    $broken = ['code' => 500,
1641
               'result' => -1,
1642
               'data' => ''];
1643
1644
    // write command
1645
    if (!@fwrite($this->out, trim($command) . "\n")) {
1646
      return $broken;
1647
    }
1648
    fflush($this->out);
1649
1650
    // Read result.  Occasionally, a command return a string followed by an extra new line.
1651
    // When this happens, our script will ignore the new line, but it will still be in the
1652
    // buffer.  So, if we get a blank line, it is probably the result of a previous
1653
    // command.  We read until we get a valid result or asterisk hangs up.  One offending
1654
    // command is SEND TEXT.
1655
    $count = 0;
1656
    do {
1657
      $str = trim(fgets($this->in, 4096));
1658
    } while ($str == '' && $count++ < 5);
1659
1660
    if ($count >= 5) {
1661
      //          $this->conlog("evaluate error on read for $command");
1662
      return $broken;
1663
    }
1664
1665
    // parse result
1666
    $ret['code'] = substr($str, 0, 3);
0 ignored issues
show
Comprehensibility Best Practice introduced by
$ret was never initialized. Although not strictly required by PHP, it is generally a good practice to add $ret = array(); before regardless.
Loading history...
1667
    $str = trim(substr($str, 3));
1668
1669
    if ($str[0] == '-') // we have a multiline response!
1670
    {
1671
      $count = 0;
1672
      $str = substr($str, 1) . "\n";
1673
      $line = fgets($this->in, 4096);
1674
      while (substr($line, 0, 3) != $ret['code'] && $count < 5) {
1675
        $str .= $line;
1676
        $line = fgets($this->in, 4096);
1677
        $count = (trim($line) == '') ? $count + 1 : 0;
1678
      }
1679
      if ($count >= 5) {
1680
        //            $this->conlog("evaluate error on multiline read for $command");
1681
        return $broken;
1682
      }
1683
    }
1684
1685
    $ret['result'] = null;
1686
    $ret['data'] = '';
1687
    if ($ret['code'] != AGIRES_OK) // some sort of error
1688
    {
1689
      $ret['data'] = $str;
1690
      $this->conlog(print_r($ret, true));
0 ignored issues
show
Bug introduced by
It seems like print_r($ret, true) can also be of type true; however, parameter $str of AGI::conlog() does only seem to accept string, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1690
      $this->conlog(/** @scrutinizer ignore-type */ print_r($ret, true));
Loading history...
1691
    } else { // normal AGIRES_OK response
1692
      $parse = explode(' ', trim($str));
1693
      $in_token = false;
1694
      foreach ($parse as $token) {
1695
        if ($in_token) { // we previously hit a token starting with ')' but not ending in ')'
1696
          $ret['data'] .= ' ' . trim($token, '() ');
1697
          if ($token[strlen($token) - 1] == ')') {
1698
            $in_token = false;
1699
          }
1700
        } elseif ($token[0] == '(') {
1701
          if ($token[strlen($token) - 1] != ')') {
1702
            $in_token = true;
1703
          }
1704
          $ret['data'] .= ' ' . trim($token, '() ');
1705
        } elseif (strpos($token, '=')) {
1706
          $token = explode('=', $token);
1707
          $ret[$token[0]] = $token[1];
1708
        } elseif ($token != '') {
1709
          $ret['data'] .= ' ' . $token;
1710
        }
1711
      }
1712
      $ret['data'] = trim($ret['data']);
1713
    }
1714
1715
    // log some errors
1716
    if ($ret['result'] < 0) {
1717
      $this->conlog("$command returned {$ret['result']}");
1718
    }
1719
1720
    return $ret;
1721
  }
1722
1723
  /**
1724
   * Log to console if debug mode.
1725
   *
1726
   * @param string $str
1727
   * @param integer $vbl verbose level
1728
   * @example examples/ping.php Ping an IP address
1729
   *
1730
   */
1731
  function conlog(string $str, int $vbl = 1) {
0 ignored issues
show
Best Practice introduced by
It is generally recommended to explicitly declare the visibility for methods.

Adding explicit visibility (private, protected, or public) is generally recommend to communicate to other developers how, and from where this method is intended to be used.

Loading history...
1732
    static $busy = false;
1733
1734
    if ($this->config['phpagi']['debug'] != false) {
1735
      if (!$busy) { // no conlogs inside conlog!!!
1736
        $busy = true;
1737
        $this->verbose($str, $vbl);
1738
        $busy = false;
0 ignored issues
show
Unused Code introduced by
The assignment to $busy is dead and can be removed.
Loading history...
1739
      }
1740
    }
1741
  }
1742
1743
  /**
1744
   * Find an executable in the path.
1745
   *
1746
   * @access private
1747
   * @param string $cmd command to find
1748
   * @param string $checkpath path to check
1749
   * @return string the path to the command
1750
   */
1751
  private function which(string $cmd, $checkpath = null) {
1752
    if (is_null($checkpath)) {
1753
      $chpath = getenv('PATH');
1754
      if ($chpath === false) {
1755
        $chpath = '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:' .
1756
          '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin';
1757
      }
1758
    } else {
1759
      $chpath = $checkpath;
1760
    }
1761
1762
    foreach (explode(':', $chpath) as $path) {
1763
      if (is_executable("$path/$cmd")) {
1764
        return "$path/$cmd";
1765
      }
1766
    }
1767
1768
    return false;
0 ignored issues
show
Bug Best Practice introduced by
The expression return false returns the type false which is incompatible with the documented return type string.
Loading history...
1769
  }
1770
1771
  /**
1772
   * Make a folder recursively.
1773
   *
1774
   * @access private
1775
   * @param string $folder
1776
   * @param integer $perms
1777
   * @return boolean
1778
   */
1779
  private function make_folder(string $folder, int $perms = 0755): bool {
1780
    $f = explode(DIRECTORY_SEPARATOR, $folder);
1781
    $base = '';
1782
    for ($i = 0; $i < count($f); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
It seems like you are calling the size function count() as part of the test condition. You might want to compute the size beforehand, and not on each iteration.

If the size of the collection does not change during the iteration, it is generally a good practice to compute it beforehand, and not on each iteration:

for ($i=0; $i<count($array); $i++) { // calls count() on each iteration
}

// Better
for ($i=0, $c=count($array); $i<$c; $i++) { // calls count() just once
}
Loading history...
1783
      $base .= $f[$i];
1784
      if ($f[$i] != '' && !file_exists($base)) {
1785
        if (mkdir($base, $perms) == false) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
1786
          return (false);
1787
        }
1788
      }
1789
      $base .= DIRECTORY_SEPARATOR;
1790
    }
1791
    return (true);
1792
  }
1793
1794
  private function proceedDigits(string $buffer, string $escapeDigits): bool {
1795
    if ($escapeDigits != "" && $buffer != "") {
1796
      if (!strpos(chr(255) . $escapeDigits, $buffer[strlen($buffer) - 1])) {
1797
        return true;
1798
      }
1799
    }
1800
    return false;
1801
  }
1802
1803
  private function ordBuffer(string $buffer): int {
1804
    return ord($buffer[strlen($buffer) - 1]);
1805
  }
1806
1807
  private function appendBuffer(string &$buffer, array $res) {
1808
    if ($res['code'] == AGIRES_OK && $res['result'] > 0) {
1809
      $buffer .= chr($res['result']);
1810
    }
1811
  }
1812
}
1813
1814
1815
/**
1816
 * error handler for phpagi.
1817
 *
1818
 * @param integer $level PHP error level
1819
 * @param string $message error message
1820
 * @param string $file path to file
1821
 * @param integer $line line number of error
1822
 * @param array $context variables in the current scope
1823
 */
1824
function phpagi_error_handler(int $level, string $message, string $file, int $line, array $context) {
1825
  if (ini_get('error_reporting') == 0) {
1826
    return; // this happens with an @
1827
  }
1828
1829
  @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for syslog(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1829
  /** @scrutinizer ignore-unhandled */ @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1830
1831
  global $phpagi_error_handler_email;
1832
  if (function_exists('mail') && !is_null($phpagi_error_handler_email)) { // generate email debugging information
1833
    // decode error level
1834
    switch ($level) {
1835
      case E_WARNING:
1836
      case E_USER_WARNING:
1837
        $level = "Warning";
1838
        break;
1839
      case E_NOTICE:
1840
      case E_USER_NOTICE:
1841
        $level = "Notice";
1842
        break;
1843
      case E_USER_ERROR:
1844
        $level = "Error";
1845
        break;
1846
    }
1847
1848
    // build message
1849
    $basefile = basename($file);
1850
    $subject = "$basefile/$line/$level: $message";
1851
    $message = "$level: $message in $file on line $line\n\n";
1852
1853
    // TODO: OLD
1854
    /*if (strpos(' ' . strtolower($message), 'mysql')) {
1855
      if (function_exists('mysql_errno')) {
1856
        $message .= 'MySQL error ' . mysql_errno() . ": " . mysql_error() . "\n\n";
1857
      } else if (function_exists('mysqli_errno')) {
1858
        $message .= 'MySQL error ' . mysqli_errno() . ": " . mysqli_error() . "\n\n";
1859
      }
1860
    }*/
1861
1862
    // figure out who we are
1863
    if (function_exists('socket_create')) {
1864
      $addr = null;
1865
      $port = 80;
1866
      $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1867
      @socket_connect($socket, '64.0.0.0', $port);
0 ignored issues
show
Bug introduced by
It seems like $socket can also be of type false; however, parameter $socket of socket_connect() does only seem to accept Socket|resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1867
      @socket_connect(/** @scrutinizer ignore-type */ $socket, '64.0.0.0', $port);
Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for socket_connect(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1867
      /** @scrutinizer ignore-unhandled */ @socket_connect($socket, '64.0.0.0', $port);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1868
      @socket_getsockname($socket, $addr, $port);
0 ignored issues
show
Bug introduced by
It seems like $socket can also be of type false; however, parameter $socket of socket_getsockname() does only seem to accept Socket|resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1868
      @socket_getsockname(/** @scrutinizer ignore-type */ $socket, $addr, $port);
Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for socket_getsockname(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1868
      /** @scrutinizer ignore-unhandled */ @socket_getsockname($socket, $addr, $port);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1869
      @socket_close($socket);
0 ignored issues
show
Bug introduced by
It seems like $socket can also be of type false; however, parameter $socket of socket_close() does only seem to accept Socket|resource, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1869
      @socket_close(/** @scrutinizer ignore-type */ $socket);
Loading history...
Bug introduced by
Are you sure the usage of socket_close($socket) is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Security Best Practice introduced by
It seems like you do not handle an error condition for socket_close(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1869
      /** @scrutinizer ignore-unhandled */ @socket_close($socket);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1870
      $message .= "\n\nIP Address: $addr\n";
1871
    }
1872
1873
    // include variables
1874
    $message .= "\n\nContext:\n" . print_r($context, true);
0 ignored issues
show
Bug introduced by
Are you sure print_r($context, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1874
    $message .= "\n\nContext:\n" . /** @scrutinizer ignore-type */ print_r($context, true);
Loading history...
1875
    $message .= "\n\nGLOBALS:\n" . print_r($GLOBALS, true);
0 ignored issues
show
Bug introduced by
Are you sure print_r($GLOBALS, true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1875
    $message .= "\n\nGLOBALS:\n" . /** @scrutinizer ignore-type */ print_r($GLOBALS, true);
Loading history...
1876
    $message .= "\n\nBacktrace:\n" . print_r(debug_backtrace(), true);
0 ignored issues
show
Bug introduced by
Are you sure print_r(debug_backtrace(), true) of type string|true can be used in concatenation? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1876
    $message .= "\n\nBacktrace:\n" . /** @scrutinizer ignore-type */ print_r(debug_backtrace(), true);
Loading history...
1877
1878
    // include code fragment
1879
    if (file_exists($file)) {
1880
      $message .= "\n\n$file:\n";
1881
      $code = @file($file);
1882
      for ($i = max(0, $line - 10); $i < min($line + 10, count($code)); $i++) {
0 ignored issues
show
Bug introduced by
It seems like $code can also be of type false; however, parameter $value of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1882
      for ($i = max(0, $line - 10); $i < min($line + 10, count(/** @scrutinizer ignore-type */ $code)); $i++) {
Loading history...
1883
        $message .= ($i + 1) . "\t$code[$i]";
1884
      }
1885
    }
1886
1887
    // make sure message is fully readable (convert unprintable chars to hex representation)
1888
    $ret = '';
1889
    for ($i = 0; $i < strlen($message); $i++) {
1890
      $c = ord($message[$i]);
1891
      if ($c == 10 || $c == 13 || $c == 9) {
1892
        $ret .= $message[$i];
1893
      } elseif ($c < 16) {
1894
        $ret .= '\x0' . dechex($c);
1895
      } elseif ($c < 32 || $c > 127) {
1896
        $ret .= '\x' . dechex($c);
1897
      } else {
1898
        $ret .= $message[$i];
1899
      }
1900
    }
1901
    $message = $ret;
1902
1903
    // send the mail if less than 5 errors
1904
    static $mailcount = 0;
1905
    if ($mailcount < 5) {
1906
      @mail($phpagi_error_handler_email, $subject, $message);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for mail(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

1906
      /** @scrutinizer ignore-unhandled */ @mail($phpagi_error_handler_email, $subject, $message);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
1907
    }
1908
    $mailcount++;
1909
  }
1910
}
1911
1912
$phpagi_error_handler_email = null;
1913
1914