Documentation is available at phpagi.php
1 <?php
2
3 /**
4 * phpagi.php : PHP AGI Functions for Asterisk
5 * Website: http://phpagi.sourceforge.net/
6 *
7 * $Id: phpagi.php,v 2.12 2005/05/25 18:43:48 pinhole Exp $
8 *
9 * Copyright (c) 2003, 2004, 2005 Matthew Asham <matthewa@bcwireless.net>, David Eder <david@eder.us>
10 * All Rights Reserved.
11 *
12 * This software is released under the terms of the GNU Lesser General Public License v2.1
13 * A copy of which is available from http://www.gnu.org/copyleft/lesser.html
14 *
15 * We would be happy to list your phpagi based application on the phpagi
16 * website. Drop me an Email if you'd like us to list your program.
17 *
18 *
19 * Written for PHP 4.3.4, should work with older PHP 4.x versions.
20 *
21 * Please submit bug reports, patches, etc to http://sourceforge.net/projects/phpagi/
22 * Gracias. :)
23 *
24 *
25 * @package phpAGI
26 * @version 2.0
27 */
28
29 /**
30 */
31
32 if(!class_exists('AGI_AsteriskManager'))
33 {
34 require_once(dirname(__FILE__) . DIRECTORY_SEPARATOR . 'phpagi-asmanager.php');
35 }
36
37 define('AST_CONFIG_DIR', '/etc/asterisk/');
38 define('AST_SPOOL_DIR', '/var/spool/asterisk/');
39 define('AST_TMP_DIR', AST_SPOOL_DIR . '/tmp/');
40 define('DEFAULT_PHPAGI_CONFIG', AST_CONFIG_DIR . '/phpagi.conf');
41
42 define('AST_DIGIT_ANY', '0123456789#*');
43
44 define('AGIRES_OK', 200);
45
46 define('AST_STATE_DOWN', 0);
47 define('AST_STATE_RESERVED', 1);
48 define('AST_STATE_OFFHOOK', 2);
49 define('AST_STATE_DIALING', 3);
50 define('AST_STATE_RING', 4);
51 define('AST_STATE_RINGING', 5);
52 define('AST_STATE_UP', 6);
53 define('AST_STATE_BUSY', 7);
54 define('AST_STATE_DIALING_OFFHOOK', 8);
55 define('AST_STATE_PRERING', 9);
56
57 define('AUDIO_FILENO', 3); // STDERR_FILENO + 1
58
59 /**
60 * AGI class
61 *
62 * @package phpAGI
63 * @link http://www.voip-info.org/wiki-Asterisk+agi
64 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
65 * @example examples/input.php Get text input from the user and say it back
66 * @example examples/ping.php Ping an IP address
67 */
68 class AGI
69 {
70 /**
71 * Request variables read in on initialization.
72 *
73 * Often contains any/all of the following:
74 * agi_request - name of agi script
75 * agi_channel - current channel
76 * agi_language - current language
77 * agi_type - channel type (SIP, ZAP, IAX, ...)
78 * agi_uniqueid - unique id based on unix time
79 * agi_callerid - callerID string
80 * agi_dnid - dialed number id
81 * agi_rdnis - referring DNIS number
82 * agi_context - current context
83 * agi_extension - extension dialed
84 * agi_priority - current priority
85 * agi_enhanced - value is 1.0 if started as an EAGI script
86 * agi_accountcode - set by SetAccount in the dialplan
87 * agi_network - value is yes if this is a fastagi
88 * agi_network_script - name of the script to execute
89 *
90 * NOTE: program arguments are still in $_SERVER['argv'].
91 *
92 * @var array
93 * @access public
94 */
95 var $request;
96
97 /**
98 * Config variables
99 *
100 * @var array
101 * @access public
102 */
103 var $config;
104
105 /**
106 * Asterisk Manager
107 *
108 * @var AGI_AsteriskManager
109 * @access public
110 */
111 var $asmanager;
112
113 /**
114 * Input Stream
115 *
116 * @access private
117 */
118 var $in = NULL;
119
120 /**
121 * Output Stream
122 *
123 * @access private
124 */
125 var $out = NULL;
126
127 /**
128 * Audio Stream
129 *
130 * @access public
131 */
132 var $audio = NULL;
133
134 /**
135 * Constructor
136 *
137 * @param string $config is the name of the config file to parse
138 * @param array $optconfig is an array of configuration vars and vals, stuffed into $this->config['phpagi']
139 */
140 function AGI($config=NULL, $optconfig=array())
141 {
142 // load config
143 if(!is_null($config) && file_exists($config))
144 $this->config = parse_ini_file($config, true);
145 elseif(file_exists(DEFAULT_PHPAGI_CONFIG))
146 $this->config = parse_ini_file(DEFAULT_PHPAGI_CONFIG, true);
147
148 // If optconfig is specified, stuff vals and vars into 'phpagi' config array.
149 foreach($optconfig as $var=>$val)
150 $this->config['phpagi'][$var] = $val;
151
152 // add default values to config for uninitialized values
153 if(!isset($this->config['phpagi']['error_handler'])) $this->config['phpagi']['error_handler'] = true;
154 if(!isset($this->config['phpagi']['debug'])) $this->config['phpagi']['debug'] = false;
155 if(!isset($this->config['phpagi']['admin'])) $this->config['phpagi']['admin'] = NULL;
156 if(!isset($this->config['phpagi']['tempdir'])) $this->config['phpagi']['tempdir'] = AST_TMP_DIR;
157
158 // festival TTS config
159 if(!isset($this->config['festival']['text2wave'])) $this->config['festival']['text2wave'] = $this->which('text2wave');
160
161 // swift TTS config
162 if(!isset($this->config['cepstral']['swift'])) $this->config['cepstral']['swift'] = $this->which('swift');
163
164 ob_implicit_flush(true);
165
166 // open stdin & stdout
167 $this->in = defined('STDIN') ? STDIN : fopen('php://stdin', 'r');
168 $this->out = defined('STDOUT') ? STDOUT : fopen('php://stdout', 'w');
169
170 // initialize error handler
171 if($this->config['phpagi']['error_handler'] == true)
172 {
173 set_error_handler('phpagi_error_handler');
174 global $phpagi_error_handler_email
175 $phpagi_error_handler_email = $this->config['phpagi']['admin'];
176 error_reporting(E_ALL);
177 }
178
179 // make sure temp folder exists
180 $this->make_folder($this->config['phpagi']['tempdir']);
181
182 // read the request
183 $str = fgets($this->in);
184 while($str != "\n")
185 {
186 $this->request[substr($str, 0, strpos($str, ':'))] = trim(substr($str, strpos($str, ':') + 1));
187 $str = fgets($this->in);
188 }
189
190 // open audio if eagi detected
191 if($this->request['agi_enhanced'] == '1.0')
192 {
193 if(file_exists('/proc/' . getmypid() . '/fd/3'))
194 $this->audio = fopen('/proc/' . getmypid() . '/fd/3', 'r');
195 elseif(file_exists('/dev/fd/3'))
196 {
197 // may need to mount fdescfs
198 $this->audio = fopen('/dev/fd/3', 'r');
199 }
200 else
201 $this->conlog('Unable to open audio stream');
202
203 if($this->audio) stream_set_blocking($this->audio, 0);
204 }
205
206 $this->conlog('AGI Request:');
207 $this->conlog(print_r($this->request, true));
208 $this->conlog('PHPAGI internal configuration:');
209 $this->conlog(print_r($this->config, true));
210 }
211
212 // *********************************************************************************************************
213 // ** COMMANDS **
214 // *********************************************************************************************************
215
216 /**
217 * Answer channel if not already in answer state.
218 *
219 * @link http://www.voip-info.org/wiki-answer
220 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
221 * @example examples/input.php Get text input from the user and say it back
222 * @example examples/ping.php Ping an IP address
223 *
224 * @return array, see evaluate for return information. ['result'] is 0 on success, -1 on failure.
225 */
226 function answer()
227 {
228 return $this->evaluate('ANSWER');
229 }
230
231 /**
232 * Get the status of the specified channel. If no channel name is specified, return the status of the current channel.
233 *
234 * @link http://www.voip-info.org/wiki-channel+status
235 * @param string $channel
236 * @return array, see evaluate for return information. ['data'] contains description.
237 */
238 function channel_status($channel='')
239 {
240 $ret = $this->evaluate("CHANNEL STATUS $channel");
241 switch($ret['result'])
242 {
243 case -1: $ret['data'] = trim("There is no channel that matches $channel"); break;
244 case AST_STATE_DOWN: $ret['data'] = 'Channel is down and available'; break;
245 case AST_STATE_RESERVED: $ret['data'] = 'Channel is down, but reserved'; break;
246 case AST_STATE_OFFHOOK: $ret['data'] = 'Channel is off hook'; break;
247 case AST_STATE_DIALING: $ret['data'] = 'Digits (or equivalent) have been dialed'; break;
248 case AST_STATE_RING: $ret['data'] = 'Line is ringing'; break;
249 case AST_STATE_RINGING: $ret['data'] = 'Remote end is ringing'; break;
250 case AST_STATE_UP: $ret['data'] = 'Line is up'; break;
251 case AST_STATE_BUSY: $ret['data'] = 'Line is busy'; break;
252 case AST_STATE_DIALING_OFFHOOK: $ret['data'] = 'Digits (or equivalent) have been dialed while offhook'; break;
253 case AST_STATE_PRERING: $ret['data'] = 'Channel has detected an incoming call and is waiting for ring'; break;
254 default: $ret['data'] = "Unknown ({$ret['result']})"; break;
255 }
256 return $ret;
257 }
258
259 /**
260 * Deletes an entry in the Asterisk database for a given family and key.
261 *
262 * @link http://www.voip-info.org/wiki-database+del
263 * @param string $family
264 * @param string $key
265 * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise.
266 */
267 function database_del($family, $key)
268 {
269 return $this->evaluate("DATABASE DEL \"$family\" \"$key\"");
270 }
271
272 /**
273 * Deletes a family or specific keytree within a family in the Asterisk database.
274 *
275 * @link http://www.voip-info.org/wiki-database+deltree
276 * @param string $family
277 * @param string $keytree
278 * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise.
279 */
280 function database_deltree($family, $keytree='')
281 {
282 $cmd = "DATABASE DELTREE \"$family\"";
283 if($keytree != '') $cmd .= " \"$keytree\"";
284 return $this->evaluate($cmd);
285 }
286
287 /**
288 * Retrieves an entry in the Asterisk database for a given family and key.
289 *
290 * @link http://www.voip-info.org/wiki-database+get
291 * @param string $family
292 * @param string $key
293 * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 failure. ['data'] holds the value
294 */
295 function database_get($family, $key)
296 {
297 return $this->evaluate("DATABASE GET \"$family\ \"$key\"");
298 }
299
300 /**
301 * Adds or updates an entry in the Asterisk database for a given family, key, and value.
302 *
303 * @param string $family
304 * @param string $key
305 * @param string $value
306 * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 otherwise
307 */
308 function database_put($family, $key, $value)
309 {
310 $value = str_replace("\n", '\n', addslashes($value));
311 return $this->evaluate("DATABASE PUT \"$family\" \"$key\" \"$value\"");
312 }
313
314 /**
315 * Executes the specified Asterisk application with given options.
316 *
317 * @link http://www.voip-info.org/wiki-exec
318 * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands
319 * @param string $application
320 * @param mixed $options
321 * @return array, see evaluate for return information. ['result'] is whatever the application returns, or -2 on failure to find application
322 */
323 function exec($application, $options)
324 {
325 if(is_array($options)) $options = join('|', $options);
326 return $this->evaluate("EXEC $application $options");
327 }
328
329 /**
330 * Plays the given file and receives DTMF data.
331 *
332 * This is similar to STREAM FILE, but this command can accept and return many DTMF digits,
333 * while STREAM FILE returns immediately after the first DTMF digit is detected.
334 *
335 * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default.
336 *
337 * If the user doesn't press any keys when the message plays, there is $timeout milliseconds
338 * of silence then the command ends.
339 *
340 * The user has the opportunity to press a key at any time during the message or the
341 * post-message silence. If the user presses a key while the message is playing, the
342 * message stops playing. When the first key is pressed a timer starts counting for
343 * $timeout milliseconds. Every time the user presses another key the timer is restarted.
344 * The command ends when the counter goes to zero or the maximum number of digits is entered,
345 * whichever happens first.
346 *
347 * If you don't specify a time out then a default timeout of 2000 is used following a pressed
348 * digit. If no digits are pressed then 6 seconds of silence follow the message.
349 *
350 * If you don't specify $max_digits then the user can enter as many digits as they want.
351 *
352 * Pressing the # key has the same effect as the timer running out: the command ends and
353 * any previously keyed digits are returned. A side effect of this is that there is no
354 * way to read a # key using this command.
355 *
356 * @example examples/ping.php Ping an IP address
357 *
358 * @link http://www.voip-info.org/wiki-get+data
359 * @param string $filename file to play. Do not include file extension.
360 * @param integer $timeout milliseconds
361 * @param integer $max_digits
362 * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present.
363 *
364 * This differs from other commands with return DTMF as numbers representing ASCII characters.
365 */
366 function get_data($filename, $timeout=NULL, $max_digits=NULL)
367 {
368 return $this->evaluate(rtrim("GET DATA $filename $timeout $max_digits"));
369 }
370
371 /**
372 * Fetch the value of a variable.
373 *
374 * Does not work with global variables. Does not work with some variables that are generated by modules.
375 *
376 * @link http://www.voip-info.org/wiki-get+variable
377 * @link http://www.voip-info.org/wiki-Asterisk+variables
378 * @param string $variable name
379 * @return array, see evaluate for return information. ['result'] is 0 if variable hasn't been set, 1 if it has. ['data'] holds the value.
380 */
381 function get_variable($variable)
382 {
383 return $this->evaluate("GET VARIABLE $variable");
384 }
385
386 /**
387 * Hangup the specified channel. If no channel name is given, hang up the current channel.
388 *
389 * With power comes responsibility. Hanging up channels other than your own isn't something
390 * that is done routinely. If you are not sure why you are doing so, then don't.
391 *
392 * @link http://www.voip-info.org/wiki-hangup
393 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
394 * @example examples/input.php Get text input from the user and say it back
395 * @example examples/ping.php Ping an IP address
396 *
397 * @param string $channel
398 * @return array, see evaluate for return information. ['result'] is 1 on success, -1 on failure.
399 */
400 function hangup($channel='')
401 {
402 return $this->evaluate("HANGUP $channel");
403 }
404
405 /**
406 * Does nothing.
407 *
408 * @link http://www.voip-info.org/wiki-noop
409 * @return array, see evaluate for return information.
410 */
411 function noop()
412 {
413 return $this->evaluate('NOOP');
414 }
415
416 /**
417 * Receive a character of text from a connected channel. Waits up to $timeout milliseconds for
418 * a character to arrive, or infinitely if $timeout is zero.
419 *
420 * @link http://www.voip-info.org/wiki-receive+char
421 * @param integer $timeout milliseconds
422 * @return array, see evaluate for return information. ['result'] is 0 on timeout or not supported, -1 on failure. Otherwise
423 * it is the decimal value of the DTMF tone. Use chr() to convert to ASCII.
424 */
425 function receive_char($timeout=-1)
426 {
427 return $this->evaluate("RECEIVE CHAR $timeout");
428 }
429
430 /**
431 * Record sound to a file until an acceptable DTMF digit is received or a specified amount of
432 * time has passed. Optionally the file BEEP is played before recording begins.
433 *
434 * @link http://www.voip-info.org/wiki-record+file
435 * @param string $file to record, without extension, often created in /var/lib/asterisk/sounds
436 * @param string $format of the file. GSM and WAV are commonly used formats. MP3 is read-only and thus cannot be used.
437 * @param string $escape_digits
438 * @param integer $timeout is the maximum record time in milliseconds, or -1 for no timeout.
439 * @param integer $offset to seek to without exceeding the end of the file.
440 * @param boolean $beep
441 * @param integer $silence number of seconds of silence allowed before the function returns despite the
442 * lack of dtmf digits or reaching timeout.
443 * @return array, see evaluate for return information. ['result'] is -1 on error, 0 on hangup, otherwise a decimal value of the
444 * DTMF tone. Use chr() to convert to ASCII.
445 */
446 function record_file($file, $format, $escape_digits='', $timeout=-1, $offset=NULL, $beep=false, $silence=NULL)
447 {
448 $cmd = trim("RECORD FILE $file $format \"$escape_digits\" $timeout $offset");
449 if($beep) $cmd .= ' BEEP';
450 if(!is_null($silence)) $cmd .= " s=$silence";
451 return $this->evaluate($cmd);
452 }
453
454 /**
455 * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel.
456 *
457 * @link http://www.voip-info.org/wiki-say+digits
458 * @param integer $digits
459 * @param string $escape_digits
460 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
461 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
462 */
463 function say_digits($digits, $escape_digits='')
464 {
465 return $this->evaluate("SAY DIGITS $digits \"$escape_digits\"");
466 }
467
468 /**
469 * Say the given number, returning early if any of the given DTMF escape digits are received on the channel.
470 *
471 * @link http://www.voip-info.org/wiki-say+number
472 * @param integer $number
473 * @param string $escape_digits
474 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
475 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
476 */
477 function say_number($number, $escape_digits='')
478 {
479 return $this->evaluate("SAY NUMBER $number \"$escape_digits\"");
480 }
481
482 /**
483 * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel.
484 *
485 * @link http://www.voip-info.org/wiki-say+phonetic
486 * @param string $text
487 * @param string $escape_digits
488 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
489 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
490 */
491 function say_phonetic($text, $escape_digits='')
492 {
493 return $this->evaluate("SAY PHONETIC $text \"$escape_digits\"");
494 }
495
496 /**
497 * Say a given time, returning early if any of the given DTMF escape digits are received on the channel.
498 *
499 * @link http://www.voip-info.org/wiki-say+time
500 * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC).
501 * @param string $escape_digits
502 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
503 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
504 */
505 function say_time($time=NULL, $escape_digits='')
506 {
507 if(is_null($time)) $time = time();
508 return $this->evaluate("SAY TIME $time \"$escape_digits\"");
509 }
510
511 /**
512 * Send the specified image on a channel.
513 *
514 * Most channels do not support the transmission of images.
515 *
516 * @link http://www.voip-info.org/wiki-send+image
517 * @param string $image without extension, often in /var/lib/asterisk/images
518 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the image is sent or
519 * channel does not support image transmission.
520 */
521 function send_image($image)
522 {
523 return $this->evaluate("SEND IMAGE $image");
524 }
525
526 /**
527 * Send the given text to the connected channel.
528 *
529 * Most channels do not support transmission of text.
530 *
531 * @link http://www.voip-info.org/wiki-send+text
532 * @param $text
533 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if the text is sent or
534 * channel does not support text transmission.
535 */
536 function send_text($text)
537 {
538 return $this->evaluate("SEND TEXT \"$text\"");
539 }
540
541 /**
542 * Cause the channel to automatically hangup at $time seconds in the future.
543 * If $time is 0 then the autohangup feature is disabled on this channel.
544 *
545 * If the channel is hungup prior to $time seconds, this setting has no effect.
546 *
547 * @link http://www.voip-info.org/wiki-set+autohangup
548 * @param integer $time until automatic hangup
549 * @return array, see evaluate for return information.
550 */
551 function set_autohangup($time=0)
552 {
553 return $this->evaluate("SET AUTOHANGUP $time");
554 }
555
556 /**
557 * Changes the caller ID of the current channel.
558 *
559 * @link http://www.voip-info.org/wiki-set+callerid
560 * @param string $cid example: "John Smith"<1234567>
561 * This command will let you take liberties with the <caller ID specification> but the format shown in the example above works
562 * well: the name enclosed in double quotes followed immediately by the number inside angle brackets. If there is no name then
563 * you can omit it. If the name contains no spaces you can omit the double quotes around it. The number must follow the name
564 * immediately; don't put a space between them. The angle brackets around the number are necessary; if you omit them the
565 * number will be considered to be part of the name.
566 * @return array, see evaluate for return information.
567 */
568 function set_callerid($cid)
569 {
570 return $this->evaluate("SET CALLERID $cid");
571 }
572
573 /**
574 * Sets the context for continuation upon exiting the application.
575 *
576 * Setting the context does NOT automatically reset the extension and the priority; if you want to start at the top of the new
577 * context you should set extension and priority yourself.
578 *
579 * If you specify a non-existent context you receive no error indication (['result'] is still 0) but you do get a
580 * warning message on the Asterisk console.
581 *
582 * @link http://www.voip-info.org/wiki-set+context
583 * @param string $context
584 * @return array, see evaluate for return information.
585 */
586 function set_context($context)
587 {
588 return $this->evaluate("SET CONTEXT $context");
589 }
590
591 /**
592 * Set the extension to be used for continuation upon exiting the application.
593 *
594 * Setting the extension does NOT automatically reset the priority. If you want to start with the first priority of the
595 * extension you should set the priority yourself.
596 *
597 * If you specify a non-existent extension you receive no error indication (['result'] is still 0) but you do
598 * get a warning message on the Asterisk console.
599 *
600 * @link http://www.voip-info.org/wiki-set+extension
601 * @param string $extension
602 * @return array, see evaluate for return information.
603 */
604 function set_extension($extension)
605 {
606 return $this->evaluate("SET EXTENSION $extension");
607 }
608
609 /**
610 * Enable/Disable Music on hold generator.
611 *
612 * @link http://www.voip-info.org/wiki-set+music
613 * @param boolean $enabled
614 * @param string $class
615 * @return array, see evaluate for return information.
616 */
617 function set_music($enabled=true, $class='')
618 {
619 $enabled = ($enabled) ? 'ON' : 'OFF';
620 return $this->evaluate("SET MUSIC $enabled $class");
621 }
622
623 /**
624 * Set the priority to be used for continuation upon exiting the application.
625 *
626 * If you specify a non-existent priority you receive no error indication (['result'] is still 0)
627 * and no warning is issued on the Asterisk console.
628 *
629 * @link http://www.voip-info.org/wiki-set+priority
630 * @param integer $priority
631 * @return array, see evaluate for return information.
632 */
633 function set_priority($priority)
634 {
635 return $this->evaluate("SET PRIORITY $priority");
636 }
637
638 /**
639 * Sets a variable to the specified value. The variables so created can later be used by later using ${<variablename>}
640 * in the dialplan.
641 *
642 * These variables live in the channel Asterisk creates when you pickup a phone and as such they are both local and temporary.
643 * Variables created in one channel can not be accessed by another channel. When you hang up the phone, the channel is deleted
644 * and any variables in that channel are deleted as well.
645 *
646 * @link http://www.voip-info.org/wiki-set+variable
647 * @param string $variable is case sensitive
648 * @param string $value
649 * @return array, see evaluate for return information.
650 */
651 function set_variable($variable, $value)
652 {
653 $value = str_replace("\n", '\n', addslashes($value));
654 return $this->evaluate("SET VARIABLE $variable \"$value\"");
655 }
656
657 /**
658 * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA
659 * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of
660 * digits before returning.
661 *
662 * @example examples/ping.php Ping an IP address
663 *
664 * @link http://www.voip-info.org/wiki-stream+file
665 * @param string $filename without extension, often in /var/lib/asterisk/sounds
666 * @param string $escape_digits
667 * @param integer $offset
668 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
669 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
670 */
671 function stream_file($filename, $escape_digits='', $offset=0)
672 {
673 return $this->evaluate("STREAM FILE $filename \"$escape_digits\" $offset");
674 }
675
676 /**
677 * Enable or disable TDD transmission/reception on the current channel.
678 *
679 * @link http://www.voip-info.org/wiki-tdd+mode
680 * @param string $setting can be on, off or mate
681 * @return array, see evaluate for return information. ['result'] is 1 on sucess, 0 if the channel is not TDD capable.
682 */
683 function tdd_mode($setting)
684 {
685 return $this->evaluate("TDD MODE $setting");
686 }
687
688 /**
689 * Sends $message to the Asterisk console via the 'verbose' message system.
690 *
691 * If the Asterisk verbosity level is $level or greater, send $message to the console.
692 *
693 * The Asterisk verbosity system works as follows. The Asterisk user gets to set the desired verbosity at startup time or later
694 * using the console 'set verbose' command. Messages are displayed on the console if their verbose level is less than or equal
695 * to desired verbosity set by the user. More important messages should have a low verbose level; less important messages
696 * should have a high verbose level.
697 *
698 * @link http://www.voip-info.org/wiki-verbose
699 * @param string $message
700 * @param integer $level from 1 to 4
701 * @return array, see evaluate for return information.
702 */
703 function verbose($message, $level=1)
704 {
705 foreach(explode("\n", str_replace("\r\n", "\n", print_r($message, true))) as $msg)
706 {
707 @syslog(LOG_WARNING, $msg);
708 $ret = $this->evaluate("VERBOSE \"$msg\" $level");
709 }
710 return $ret;
711 }
712
713 /**
714 * Waits up to $timeout milliseconds for channel to receive a DTMF digit.
715 *
716 * @link http://www.voip-info.org/wiki-wait+for+digit
717 * @param integer $timeout in millisecons. Use -1 for the timeout value if you want the call to wait indefinitely.
718 * @return array, see evaluate for return information. ['result'] is 0 if wait completes with no
719 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
720 */
721 function wait_for_digit($timeout=-1)
722 {
723 return $this->evaluate("WAIT FOR DIGIT $timeout");
724 }
725
726
727 // *********************************************************************************************************
728 // ** APPLICATIONS **
729 // *********************************************************************************************************
730
731 /**
732 * Set absolute maximum time of call.
733 *
734 * Note that the timeout is set from the current time forward, not counting the number of seconds the call has already been up.
735 * Each time you call AbsoluteTimeout(), all previous absolute timeouts are cancelled.
736 * Will return the call to the T extension so that you can playback an explanatory note to the calling party (the called party
737 * will not hear that)
738 *
739 * @link http://www.voip-info.org/wiki-Asterisk+-+documentation+of+application+commands
740 * @link http://www.dynx.net/ASTERISK/AGI/ccard/agi-ccard.agi
741 * @param $seconds allowed, 0 disables timeout
742 * @return array, see evaluate for return information.
743 */
744 function exec_absolutetimeout($seconds=0)
745 {
746 return $this->exec('AbsoluteTimeout', $seconds);
747 }
748
749 /**
750 * Executes an AGI compliant application.
751 *
752 * @param string $command
753 * @return array, see evaluate for return information. ['result'] is -1 on hangup or if application requested hangup, or 0 on non-hangup exit.
754 * @param string $args
755 */
756 function exec_agi($command, $args)
757 {
758 return $this->exec("AGI $command", $args);
759 }
760
761 /**
762 * Set Language.
763 *
764 * @param string $language code
765 * @return array, see evaluate for return information.
766 */
767 function exec_setlanguage($language='en')
768 {
769 return $this->exec('SetLanguage', $language);
770 }
771
772 /**
773 * Do ENUM Lookup.
774 *
775 * Note: to retrieve the result, use
776 * get_variable('ENUM');
777 *
778 * @param $exten
779 * @return array, see evaluate for return information.
780 */
781 function exec_enumlookup($exten)
782 {
783 return $this->exec('EnumLookup', $exten);
784 }
785
786 /**
787 * Dial.
788 *
789 * Dial takes input from ${VXML_URL} to send XML Url to Cisco 7960
790 * Dial takes input from ${ALERT_INFO} to set ring cadence for Cisco phones
791 * Dial returns ${CAUSECODE}: If the dial failed, this is the errormessage.
792 * Dial returns ${DIALSTATUS}: Text code returning status of last dial attempt.
793 *
794 * @link http://www.voip-info.org/wiki-Asterisk+cmd+Dial
795 * @param string $type
796 * @param string $identifier
797 * @param integer $timeout
798 * @param string $options
799 * @param string $url
800 * @return array, see evaluate for return information.
801 */
802 function exec_dial($type, $identifier, $timeout=NULL, $options=NULL, $url=NULL)
803 {
804 return $this->exec('Dial', trim("$type/$identifier|$timeout|$options|$url", '|'));
805 }
806
807 /**
808 * Goto.
809 *
810 * This function takes three arguments: context,extension, and priority, but the leading arguments
811 * are optional, not the trailing arguments. Thuse goto($z) sets the priority to $z.
812 *
813 * @param string $a
814 * @param string $b;
815 * @param string $c;
816 * @return array, see evaluate for return information.
817 */
818 function exec_goto($a, $b=NULL, $c=NULL)
819 {
820 return $this->exec('Goto', trim("$a|$b|$c", '|'));
821 }
822
823
824 // *********************************************************************************************************
825 // ** FAST PASSING **
826 // *********************************************************************************************************
827
828 /**
829 * Say the given digit string, returning early if any of the given DTMF escape digits are received on the channel.
830 * Return early if $buffer is adequate for request.
831 *
832 * @link http://www.voip-info.org/wiki-say+digits
833 * @param string $buffer
834 * @param integer $digits
835 * @param string $escape_digits
836 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
837 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
838 */
839 function fastpass_say_digits(&$buffer, $digits, $escape_digits='')
840 {
841 $proceed = false;
842 if($escape_digits != '' && $buffer != '')
843 {
844 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
845 $proceed = true;
846 }
847 if($buffer == '' || $proceed)
848 {
849 $res = $this->say_digits($digits, $escape_digits);
850 if($res['code'] == AGIRES_OK && $res['result'] > 0)
851 $buffer .= chr($res['result']);
852 return $res;
853 }
854 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}));
855 }
856
857 /**
858 * Say the given number, returning early if any of the given DTMF escape digits are received on the channel.
859 * Return early if $buffer is adequate for request.
860 *
861 * @link http://www.voip-info.org/wiki-say+number
862 * @param string $buffer
863 * @param integer $number
864 * @param string $escape_digits
865 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
866 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
867 */
868 function fastpass_say_number(&$buffer, $number, $escape_digits='')
869 {
870 $proceed = false;
871 if($escape_digits != '' && $buffer != '')
872 {
873 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
874 $proceed = true;
875 }
876 if($buffer == '' || $proceed)
877 {
878 $res = $this->say_number($number, $escape_digits);
879 if($res['code'] == AGIRES_OK && $res['result'] > 0)
880 $buffer .= chr($res['result']);
881 return $res;
882 }
883 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}));
884 }
885
886 /**
887 * Say the given character string, returning early if any of the given DTMF escape digits are received on the channel.
888 * Return early if $buffer is adequate for request.
889 *
890 * @link http://www.voip-info.org/wiki-say+phonetic
891 * @param string $buffer
892 * @param string $text
893 * @param string $escape_digits
894 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
895 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
896 */
897 function fastpass_say_phonetic(&$buffer, $text, $escape_digits='')
898 {
899 $proceed = false;
900 if($escape_digits != '' && $buffer != '')
901 {
902 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
903 $proceed = true;
904 }
905 if($buffer == '' || $proceed)
906 {
907 $res = $this->say_phonetic($text, $escape_digits);
908 if($res['code'] == AGIRES_OK && $res['result'] > 0)
909 $buffer .= chr($res['result']);
910 return $res;
911 }
912 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}));
913 }
914
915 /**
916 * Say a given time, returning early if any of the given DTMF escape digits are received on the channel.
917 * Return early if $buffer is adequate for request.
918 *
919 * @link http://www.voip-info.org/wiki-say+time
920 * @param string $buffer
921 * @param integer $time number of seconds elapsed since 00:00:00 on January 1, 1970, Coordinated Universal Time (UTC).
922 * @param string $escape_digits
923 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
924 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
925 */
926 function fastpass_say_time(&$buffer, $time=NULL, $escape_digits='')
927 {
928 $proceed = false;
929 if($escape_digits != '' && $buffer != '')
930 {
931 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
932 $proceed = true;
933 }
934 if($buffer == '' || $proceed)
935 {
936 $res = $this->say_time($time, $escape_digits);
937 if($res['code'] == AGIRES_OK && $res['result'] > 0)
938 $buffer .= chr($res['result']);
939 return $res;
940 }
941 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}));
942 }
943
944 /**
945 * Play the given audio file, allowing playback to be interrupted by a DTMF digit. This command is similar to the GET DATA
946 * command but this command returns after the first DTMF digit has been pressed while GET DATA can accumulated any number of
947 * digits before returning.
948 * Return early if $buffer is adequate for request.
949 *
950 * @link http://www.voip-info.org/wiki-stream+file
951 * @param string $buffer
952 * @param string $filename without extension, often in /var/lib/asterisk/sounds
953 * @param string $escape_digits
954 * @param integer $offset
955 * @return array, see evaluate for return information. ['result'] is -1 on hangup or error, 0 if playback completes with no
956 * digit received, otherwise a decimal value of the DTMF tone. Use chr() to convert to ASCII.
957 */
958 function fastpass_stream_file(&$buffer, $filename, $escape_digits='', $offset=0)
959 {
960 $proceed = false;
961 if($escape_digits != '' && $buffer != '')
962 {
963 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
964 $proceed = true;
965 }
966 if($buffer == '' || $proceed)
967 {
968 $res = $this->stream_file($filename, $escape_digits, $offset);
969 if($res['code'] == AGIRES_OK && $res['result'] > 0)
970 $buffer .= chr($res['result']);
971 return $res;
972 }
973 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0);
974 }
975
976 /**
977 * Use festival to read text.
978 * Return early if $buffer is adequate for request.
979 *
980 * @link http://www.cstr.ed.ac.uk/projects/festival/
981 * @param string $buffer
982 * @param string $text
983 * @param string $escape_digits
984 * @param integer $frequency
985 * @return array, see evaluate for return information.
986 */
987 function fastpass_text2wav(&$buffer, $text, $escape_digits='', $frequency=8000)
988 {
989 $proceed = false;
990 if($escape_digits != '' && $buffer != '')
991 {
992 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
993 $proceed = true;
994 }
995 if($buffer == '' || $proceed)
996 {
997 $res = $this->text2wav($text, $escape_digits, $frequency);
998 if($res['code'] == AGIRES_OK && $res['result'] > 0)
999 $buffer .= chr($res['result']);
1000 return $res;
1001 }
1002 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0);
1003 }
1004
1005 /**
1006 * Use Cepstral Swift to read text.
1007 * Return early if $buffer is adequate for request.
1008 *
1009 * @link http://www.cepstral.com/
1010 * @param string $buffer
1011 * @param string $text
1012 * @param string $escape_digits
1013 * @param integer $frequency
1014 * @return array, see evaluate for return information.
1015 */
1016 function fastpass_swift(&$buffer, $text, $escape_digits='', $frequency=8000, $voice=NULL)
1017 {
1018 $proceed = false;
1019 if($escape_digits != '' && $buffer != '')
1020 {
1021 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
1022 $proceed = true;
1023 }
1024 if($buffer == '' || $proceed)
1025 {
1026 $res = $this->swift($text, $escape_digits, $frequency, $voice);
1027 if($res['code'] == AGIRES_OK && $res['result'] > 0)
1028 $buffer .= chr($res['result']);
1029 return $res;
1030 }
1031 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}), 'endpos'=>0);
1032 }
1033
1034 /**
1035 * Say Puncutation in a string.
1036 * Return early if $buffer is adequate for request.
1037 *
1038 * @param string $buffer
1039 * @param string $text
1040 * @param string $escape_digits
1041 * @param integer $frequency
1042 * @return array, see evaluate for return information.
1043 */
1044 function fastpass_say_punctuation(&$buffer, $text, $escape_digits='', $frequency=8000)
1045 {
1046 $proceed = false;
1047 if($escape_digits != '' && $buffer != '')
1048 {
1049 if(!strpos(chr(255) . $escape_digits, $buffer{strlen($buffer)-1}))
1050 $proceed = true;
1051 }
1052 if($buffer == '' || $proceed)
1053 {
1054 $res = $this->say_punctuation($text, $escape_digits, $frequency);
1055 if($res['code'] == AGIRES_OK && $res['result'] > 0)
1056 $buffer .= chr($res['result']);
1057 return $res;
1058 }
1059 return array('code'=>AGIRES_OK, 'result'=>ord($buffer{strlen($buffer)-1}));
1060 }
1061
1062 /**
1063 * Plays the given file and receives DTMF data.
1064 * Return early if $buffer is adequate for request.
1065 *
1066 * This is similar to STREAM FILE, but this command can accept and return many DTMF digits,
1067 * while STREAM FILE returns immediately after the first DTMF digit is detected.
1068 *
1069 * Asterisk looks for the file to play in /var/lib/asterisk/sounds by default.
1070 *
1071 * If the user doesn't press any keys when the message plays, there is $timeout milliseconds
1072 * of silence then the command ends.
1073 *
1074 * The user has the opportunity to press a key at any time during the message or the
1075 * post-message silence. If the user presses a key while the message is playing, the
1076 * message stops playing. When the first key is pressed a timer starts counting for
1077 * $timeout milliseconds. Every time the user presses another key the timer is restarted.
1078 * The command ends when the counter goes to zero or the maximum number of digits is entered,
1079 * whichever happens first.
1080 *
1081 * If you don't specify a time out then a default timeout of 2000 is used following a pressed
1082 * digit. If no digits are pressed then 6 seconds of silence follow the message.
1083 *
1084 * If you don't specify $max_digits then the user can enter as many digits as they want.
1085 *
1086 * Pressing the # key has the same effect as the timer running out: the command ends and
1087 * any previously keyed digits are returned. A side effect of this is that there is no
1088 * way to read a # key using this command.
1089 *
1090 * @link http://www.voip-info.org/wiki-get+data
1091 * @param string $buffer
1092 * @param string $filename file to play. Do not include file extension.
1093 * @param integer $timeout milliseconds
1094 * @param integer $max_digits
1095 * @return array, see evaluate for return information. ['result'] holds the digits and ['data'] holds the timeout if present.
1096 *
1097 * This differs from other commands with return DTMF as numbers representing ASCII characters.
1098 */
1099 function fastpass_get_data(&$buffer, $filename, $timeout=NULL, $max_digits=NULL)
1100 {
1101 if(is_null($max_digits) || strlen($buffer) < $max_digits)
1102 {
1103 if($buffer == '')
1104 {
1105 $res = $this->get_data($filename, $timeout, $max_digits);
1106 if($res['code'] == AGIRES_OK)
1107 $buffer .= $res['result'];
1108 return $res;
1109 }
1110 else
1111 {
1112 while(is_null($max_digits) || strlen($buffer) < $max_digits)
1113 {
1114 $res = $this->wait_for_digit();
1115 if($res['code'] != AGIRES_OK) return $res;
1116 if($res['result'] == ord('#')) break;
1117 $buffer .= chr($res['result']);
1118 }
1119 }
1120 }
1121 return array('code'=>AGIRES_OK, 'result'=>$buffer);
1122 }
1123
1124 // *********************************************************************************************************
1125 // ** DERIVED **
1126 // *********************************************************************************************************
1127
1128 /**
1129 * Menu.
1130 *
1131 * This function presents the user with a menu and reads the response
1132 *
1133 * @param array $choices has the following structure:
1134 * array('1'=>'*Press 1 for this', // festival reads if prompt starts with *
1135 * '2'=>'some-gsm-without-extension',
1136 * '*'=>'*Press star for help');
1137 * @return mixed key pressed on sucess, -1 on failure
1138 */
1139 function menu($choices, $timeout=2000)
1140 {
1141 $keys = join('', array_keys($choices));
1142 $choice = NULL;
1143 while(is_null($choice))
1144 {
1145 foreach($choices as $prompt)
1146 {
1147 if($prompt{0} == '*')
1148 $ret = $this->text2wav(substr($prompt, 1), $keys);
1149 else
1150 $ret = $this->stream_file($prompt, $keys);
1151
1152 if($ret['code'] != AGIRES_OK || $ret['result'] == -1)
1153 {
1154 $choice = -1;
1155 break;
1156 }
1157
1158 if($ret['result'] != 0)
1159 {
1160 $choice = chr($ret['result']);
1161 break;
1162 }
1163 }
1164
1165 if(is_null($choice))
1166 {
1167 $ret = $this->get_data('beep', $timeout, 1);
1168 if($ret['code'] != AGIRES_OK || $ret['result'] == -1)
1169 $choice = -1;
1170 elseif($ret['result'] != '' && strpos(' '.$keys, $ret['result']))
1171 $choice = $ret['result'];
1172 }
1173 }
1174 return $choice;
1175 }
1176
1177 /**
1178 * Goto - Set context, extension and priority.
1179 *
1180 * @param string $context
1181 * @param string $extension
1182 * @param string $priority
1183 */
1184 function goto($context, $extension='s', $priority=1)
1185 {
1186 $this->set_context($context);
1187 $this->set_extension($extension);
1188 $this->set_priority($priority);
1189 }
1190
1191 /**
1192 * Parse caller id.
1193 *
1194 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
1195 * @example examples/input.php Get text input from the user and say it back
1196 *
1197 * "name" <proto:user@server:port>
1198 *
1199 * @param string $callerid
1200 * @return array('Name'=>$name, 'Number'=>$number)
1201 */
1202 function parse_callerid($callerid=NULL)
1203 {
1204 if(is_null($callerid))
1205 $callerid = $this->request['agi_callerid'];
1206
1207 $ret = array('name'=>'', 'protocol'=>'', 'username'=>'', 'host'=>'', 'port'=>'');
1208 $callerid = trim($callerid);
1209
1210 if($callerid{0} == '"' || $callerid{0} == "'")
1211 {
1212 $d = $callerid{0};
1213 $callerid = explode($d, substr($callerid, 1));
1214 $ret['name'] = array_shift($callerid);
1215 $callerid = join($d, $callerid);
1216 }
1217
1218 $callerid = explode('@', trim($callerid, '<> '));
1219 $username = explode(':', array_shift($callerid));
1220 if(count($username) == 1)
1221 $ret['username'] = $username[0];
1222 else
1223 {
1224 $ret['protocol'] = array_shift($username);
1225 $ret['username'] = join(':', $username);
1226 }
1227
1228 $callerid = join('@', $callerid);
1229 $host = explode(':', $callerid);
1230 if(count($host) == 1)
1231 $ret['host'] = $host[0];
1232 else
1233 {
1234 $ret['host'] = array_shift($host);
1235 $ret['port'] = join(':', $host);
1236 }
1237
1238 return $ret;
1239 }
1240
1241 /**
1242 * Use festival to read text.
1243 *
1244 * @example examples/dtmf.php Get DTMF tones from the user and say the digits
1245 * @example examples/input.php Get text input from the user and say it back
1246 * @example examples/ping.php Ping an IP address
1247 *
1248 * @link http://www.cstr.ed.ac.uk/projects/festival/
1249 * @param string $text
1250 * @param string $escape_digits
1251 * @param integer $frequency
1252 * @return array, see evaluate for return information.
1253 */
1254 function text2wav($text, $escape_digits='', $frequency=8000)
1255 {
1256 $text = trim($text);
1257 if($text == '') return true;
1258
1259 $hash = md5($text);
1260 $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR;
1261 $fname .= 'text2wav_' . $hash;
1262
1263 // create wave file
1264 if(!file_exists("$fname.wav"))
1265 {
1266 // write text file
1267 if(!file_exists("$fname.txt"))
1268 {
1269 $fp = fopen("$fname.txt", 'w');
1270 fputs($fp, $text);
1271 fclose($fp);
1272 }
1273
1274 shell_exec("{$this->config['festival']['text2wave']} -F $frequency -o $fname.wav $fname.txt");
1275 }
1276 else
1277 {
1278 touch("$fname.txt");
1279 touch("$fname.wav");
1280 }
1281
1282 // stream it
1283 $ret = $this->stream_file($fname, $escape_digits);
1284
1285 // clean up old files
1286 $delete = time() - 2592000; // 1 month
1287 foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'text2wav_*') as $file)
1288 if(filemtime($file) < $delete)
1289 unlink($file);
1290
1291 return $ret;
1292 }
1293
1294 /**
1295 * Use Cepstral Swift to read text.
1296 *
1297 * @link http://www.cepstral.com/
1298 * @param string $text
1299 * @param string $escape_digits
1300 * @param integer $frequency
1301 * @return array, see evaluate for return information.
1302 */
1303 function swift($text, $escape_digits='', $frequency=8000, $voice=NULL)
1304 {
1305 if(!is_null($voice))
1306 $voice = "-n $voice";
1307 elseif(isset($this->config['cepstral']['voice']))
1308 $voice = "-n {$this->config['cepstral']['voice']}";
1309
1310 $text = trim($text);
1311 if($text == '') return true;
1312
1313 $hash = md5($text);
1314 $fname = $this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR;
1315 $fname .= 'swift_' . $hash;
1316
1317 // create wave file
1318 if(!file_exists("$fname.wav"))
1319 {
1320 // write text file
1321 if(!file_exists("$fname.txt"))
1322 {
1323 $fp = fopen("$fname.txt", 'w');
1324 fputs($fp, $text);
1325 fclose($fp);
1326 }
1327
1328 shell_exec("{$this->config['cepstral']['swift']} -p audio/channels=1,audio/sampling-rate=$frequency $voice -o $fname.wav -f $fname.txt");
1329 }
1330
1331 // stream it
1332 $ret = $this->stream_file($fname, $escape_digits);
1333
1334 // clean up old files
1335 $delete = time() - 2592000; // 1 month
1336 foreach(glob($this->config['phpagi']['tempdir'] . DIRECTORY_SEPARATOR . 'swift_*') as $file)
1337 if(filemtime($file) < $delete)
1338 unlink($file);
1339
1340 return $ret;
1341 }
1342
1343 /**
1344 * Text Input.
1345 *
1346 * Based on ideas found at http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText
1347 *
1348 * Example:
1349 * UC H LC i , SP h o w SP a r e SP y o u ?
1350 * $string = '*8'.'44*'.'*5'.'444*'.'00*'.'0*'.'44*'.'666*'.'9*'.'0*'.'2*'.'777*'.'33*'.'0*'.'999*'.'666*'.'88*'.'0000*';
1351 *
1352 * @link http://www.voip-info.org/wiki-Asterisk+cmd+DTMFToText
1353 * @example examples/input.php Get text input from the user and say it back
1354 *
1355 * @return string
1356 */
1357 function text_input($mode='NUMERIC')
1358 {
1359 $alpha = array( 'k0'=>' ', 'k00'=>',', 'k000'=>'.', 'k0000'=>'?', 'k00000'=>'0',
1360 'k1'=>'!', 'k11'=>':', 'k111'=>';', 'k1111'=>'#', 'k11111'=>'1',
1361 'k2'=>'A', 'k22'=>'B', 'k222'=>'C', 'k2222'=>'2',
1362 'k3'=>'D', 'k33'=>'E', 'k333'=>'F', 'k3333'=>'3',
1363 'k4'=>'G', 'k44'=>'H', 'k444'=>'I', 'k4444'=>'4',
1364 'k5'=>'J', 'k55'=>'K', 'k555'=>'L', 'k5555'=>'5',
1365 'k6'=>'M', 'k66'=>'N', 'k666'=>'O', 'k6666'=>'6',
1366 'k7'=>'P', 'k77'=>'Q', 'k777'=>'R', 'k7777'=>'S', 'k77777'=>'7',
1367 'k8'=>'T', 'k88'=>'U', 'k888'=>'V', 'k8888'=>'8',
1368 'k9'=>'W', 'k99'=>'X', 'k999'=>'Y', 'k9999'=>'Z', 'k99999'=>'9');
1369 $symbol = array('k0'=>'=',
1370 'k1'=>'<', 'k11'=>'(', 'k111'=>'[', 'k1111'=>'{', 'k11111'=>'1',
1371 'k2'=>'@', 'k22'=>'$', 'k222'=>'&', 'k2222'=>'%', 'k22222'=>'2',
1372 'k3'=>'>', 'k33'=>')', 'k333'=>']', 'k3333'=>'}', 'k33333'=>'3',
1373 'k4'=>'+', 'k44'=>'-', 'k444'=>'*', 'k4444'=>'/', 'k44444'=>'4',
1374 'k5'=>"'", 'k55'=>'`', 'k555'=>'5',
1375 'k6'=>'"', 'k66'=>'6',
1376 'k7'=>'^', 'k77'=>'7',
1377 'k8'=>"\\",'k88'=>'|', 'k888'=>'8',
1378 'k9'=>'_', 'k99'=>'~', 'k999'=>'9');
1379 $text = '';
1380 do
1381 {
1382 $command = false;
1383 $result = $this->get_data('beep');
1384 foreach(explode('*', $result['result']) as $code)
1385 {
1386 if($command)
1387 {
1388 switch($code{0})
1389 {
1390 case '2': $text = substr($text, 0, strlen($text) - 1); break; // backspace
1391 case '5': $mode = 'LOWERCASE'; break;
1392 case '6': $mode = 'NUMERIC'; break;
1393 case '7': $mode = 'SYMBOL'; break;
1394 case '8': $mode = 'UPPERCASE'; break;
1395 case '9': $text = explode(' ', $text); unset($text[count($text)-1]); $text = join(' ', $text); break; // backspace a word
1396 }
1397 $code = substr($code, 1);
1398 $command = false;
1399 }
1400 if($code == '')
1401 $command = true;
1402 elseif($mode == 'NUMERIC')
1403 $text .= $code;
1404 elseif($mode == 'UPPERCASE' && isset($alpha['k'.$code]))
1405 $text .= $alpha['k'.$code];
1406 elseif($mode == 'LOWERCASE' && isset($alpha['k'.$code]))
1407 $text .= strtolower($alpha['k'.$code]);
1408 elseif($mode == 'SYMBOL' && isset($symbol['k'.$code]))
1409 $text .= $symbol['k'.$code];
1410 }
1411 $this->say_punctuation($text);
1412 } while(substr($result['result'], -2) == '**');
1413 return $text;
1414 }
1415
1416 /**
1417 * Say Puncutation in a string.
1418 *
1419 * @param string $text
1420 * @param string $escape_digits
1421 * @param integer $frequency
1422 * @return array, see evaluate for return information.
1423 */
1424 function say_punctuation($text, $escape_digits='', $frequency=8000)
1425 {
1426 for($i = 0; $i < strlen($text); $i++)
1427 {
1428 switch($text{$i})
1429 {
1430 case ' ': $ret .= 'SPACE ';
1431 case ',': $ret .= 'COMMA '; break;
1432 case '.': $ret .= 'PERIOD '; break;
1433 case '?': $ret .= 'QUESTION MARK '; break;
1434 case '!': $ret .= 'EXPLANATION POINT '; break;
1435 case ':': $ret .= 'COLON '; break;
1436 case ';': $ret .= 'SEMICOLON '; break;
1437 case '#': $ret .= 'POUND '; break;
1438 case '=': $ret .= 'EQUALS '; break;
1439 case '<': $ret .= 'LESS THAN '; break;
1440 case '(': $ret .= 'LEFT PARENTHESIS '; break;
1441 case '[': $ret .= 'LEFT BRACKET '; break;
1442 case '{': $ret .= 'LEFT BRACE '; break;
1443 case '@': $ret .= 'AT '; break;
1444 case '$': $ret .= 'DOLLAR SIGN '; break;
1445 case '&': $ret .= 'AMPERSAND '; break;
1446 case '%': $ret .= 'PERCENT '; break;
1447 case '>': $ret .= 'GREATER THAN '; break;
1448 case ')': $ret .= 'RIGHT PARENTHESIS '; break;
1449 case ']': $ret .= 'RIGHT BRACKET '; break;
1450 case '}': $ret .= 'RIGHT BRACE '; break;
1451 case '+': $ret .= 'PLUS '; break;
1452 case '-': $ret .= 'MINUS '; break;
1453 case '*': $ret .= 'ASTERISK '; break;
1454 case '/': $ret .= 'SLASH '; break;
1455 case "'": $ret .= 'SINGLE QUOTE '; break;
1456 case '`': $ret .= 'BACK TICK '; break;
1457 case '"': $ret .= 'QUOTE '; break;
1458 case '^': $ret .= 'CAROT '; break;
1459 case "\\": $ret .= 'BACK SLASH '; break;
1460 case '|': $ret .= 'BAR '; break;
1461 case '_': $ret .= 'UNDERSCORE '; break;
1462 case '~': $ret .= 'TILDE '; break;
1463 default: $ret .= $text{$i} . ' '; break;
1464 }
1465 }
1466 return $this->text2wav($ret, $escape_digits, $frequency);
1467 }
1468
1469 /**
1470 * Create a new AGI_AsteriskManager.
1471 */
1472 function &new_AsteriskManager()
1473 {
1474 $this->asm = new AGI_AsteriskManager(NULL, $this->config);
1475 $this->asm->pagi =& $this;
1476 $this->config =& $this->asm->config;
1477 return $this->asm;
1478 }
1479
1480
1481 // *********************************************************************************************************
1482 // ** PRIVATE **
1483 // *********************************************************************************************************
1484
1485
1486 /**
1487 * Evaluate an AGI command.
1488 *
1489 * @access private
1490 * @param string $command
1491 * @return array ('code'=>$code, 'result'=>$result, 'data'=>$data)
1492 */
1493 function evaluate($command)
1494 {
1495 $broken = array('code'=>500, 'result'=>-1, 'data'=>'');
1496
1497 // write command
1498 if(!@fwrite($this->out, trim($command) . "\n")) return $broken;
1499 fflush($this->out);
1500
1501 // Read result. Occasionally, a command return a string followed by an extra new line.
1502 // When this happens, our script will ignore the new line, but it will still be in the
1503 // buffer. So, if we get a blank line, it is probably the result of a previous
1504 // command. We read until we get a valid result or asterisk hangs up. One offending
1505 // command is SEND TEXT.
1506 $count = 0;
1507 do
1508 {
1509 $str = trim(fgets($this->in, 4096));
1510 } while($str == '' && $count++ < 5);
1511
1512 if($count >= 5)
1513 {
1514 // $this->conlog("evaluate error on read for $command");
1515 return $broken;
1516 }
1517
1518 // parse result
1519 $ret['code'] = substr($str, 0, 3);
1520 $str = trim(substr($str, 3));
1521
1522 if($str{0} == '-') // we have a multiline response!
1523 {
1524 $count = 0;
1525 $str = substr($str, 1) . "\n";
1526 $line = fgets($this->in, 4096);
1527 while(substr($line, 0, 3) != $ret['code'] && $count < 5)
1528 {
1529 $str .= $line;
1530 $line = fgets($this->in, 4096);
1531 $count = (trim($line) == '') ? $count + 1 : 0;
1532 }
1533 if($count >= 5)
1534 {
1535 // $this->conlog("evaluate error on multiline read for $command");
1536 return $broken;
1537 }
1538 }
1539
1540 $ret['result'] = NULL;
1541 $ret['data'] = '';
1542 if($ret['code'] != AGIRES_OK) // some sort of error
1543 {
1544 $ret['data'] = $str;
1545 $this->conlog(print_r($ret, true));
1546 }
1547 else // normal AGIRES_OK response
1548 {
1549 $parse = explode(' ', trim($str));
1550 $in_token = false;
1551 foreach($parse as $token)
1552 {
1553 if($in_token) // we previously hit a token starting with ')' but not ending in ')'
1554 {
1555 $ret['data'] .= ' ' . trim($token, '() ');
1556 if($token{strlen($token)-1} == ')') $in_token = false;
1557 }
1558 elseif($token{0} == '(')
1559 {
1560 if($token{strlen($token)-1} != ')') $in_token = true;
1561 $ret['data'] .= ' ' . trim($token, '() ');
1562 }
1563 elseif(strpos($token, '='))
1564 {
1565 $token = explode('=', $token);
1566 $ret[$token[0]] = $token[1];
1567 }
1568 elseif($token != '')
1569 $ret['data'] .= ' ' . $token;
1570 }
1571 $ret['data'] = trim($ret['data']);
1572 }
1573
1574 // log some errors
1575 if($ret['result'] < 0)
1576 $this->conlog("$command returned {$ret['result']}");
1577
1578 return $ret;
1579 }
1580
1581 /**
1582 * Log to console if debug mode.
1583 *
1584 * @example examples/ping.php Ping an IP address
1585 *
1586 * @param string $str
1587 * @param integer $vbl verbose level
1588 */
1589 function conlog($str, $vbl=1)
1590 {
1591 static $busy = false;
1592
1593 if($this->config['phpagi']['debug'] != false)
1594 {
1595 if(!$busy) // no conlogs inside conlog!!!
1596 {
1597 $busy = true;
1598 $this->verbose($str, $vbl);
1599 $busy = false;
1600 }
1601 }
1602 }
1603
1604 /**
1605 * Find an execuable in the path.
1606 *
1607 * @access private
1608 * @param string $cmd command to find
1609 * @param string $checkpath path to check
1610 * @return string the path to the command
1611 */
1612 function which($cmd, $checkpath=NULL)
1613 {
1614 global $_ENV
1615 $chpath = is_null($checkpath) ? $_ENV['PATH'] : $checkpath;
1616
1617 foreach(explode(':', $chpath) as $path)
1618 if(is_executable("$path/$cmd"))
1619 return "$path/$cmd";
1620
1621 if(is_null($checkpath))
1622 return $this->which($cmd, '/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/bin:/usr/local/sbin:'.
1623 '/usr/X11R6/bin:/usr/local/apache/bin:/usr/local/mysql/bin');
1624 return false;
1625 }
1626
1627 /**
1628 * Make a folder recursively.
1629 *
1630 * @access private
1631 * @param string $folder
1632 * @param integer $perms
1633 */
1634 function make_folder($folder, $perms=0755)
1635 {
1636 $f = explode(DIRECTORY_SEPARATOR, $folder);
1637 $base = '';
1638 for($i = 0; $i < count($f); $i++)
1639 {
1640 $base .= $f[$i];
1641 if($f[$i] != '' && !file_exists($base))
1642 mkdir($base, $perms);
1643 $base .= DIRECTORY_SEPARATOR;
1644 }
1645 }
1646 }
1647
1648 /**
1649 * error handler for phpagi.
1650 *
1651 * @param integer $level PHP error level
1652 * @param string $message error message
1653 * @param string $file path to file
1654 * @param integer $line line number of error
1655 * @param array $context variables in the current scope
1656 */
1657 function phpagi_error_handler($level, $message, $file, $line, $context)
1658 {
1659 if(ini_get('error_reporting') == 0) return; // this happens with an @
1660
1661 @syslog(LOG_WARNING, $file . '[' . $line . ']: ' . $message);
1662
1663 global $phpagi_error_handler_email
1664 if(function_exists('mail') && !is_null($phpagi_error_handler_email)) // generate email debugging information
1665 {
1666 // decode error level
1667 switch($level)
1668 {
1669 case E_WARNING:
1670 case E_USER_WARNING:
1671 $level = "Warning";
1672 break;
1673 case E_NOTICE:
1674 case E_USER_NOTICE:
1675 $level = "Notice";
1676 break;
1677 case E_USER_ERROR:
1678 $level = "Error";
1679 break;
1680 }
1681
1682 // build message
1683 $basefile = basename($file);
1684 $subject = "$basefile/$line/$level: $message";
1685 $message = "$level: $message in $file on line $line\n\n";
1686
1687 if(function_exists('mysql_errno') && strpos(' '.strtolower($message), 'mysql'))
1688 $message .= 'MySQL error ' . mysql_errno() . ": " . mysql_error() . "\n\n";
1689
1690 // figure out who we are
1691 if(function_exists('socket_create'))
1692 {
1693 $addr = NULL;
1694 $port = 80;
1695 $socket = @socket_create(AF_INET, SOCK_DGRAM, SOL_UDP);
1696 @socket_connect($socket, '64.0.0.0', $port);
1697 @socket_getsockname($socket, $addr, $port);
1698 @socket_close($socket);
1699 $message .= "\n\nIP Address: $addr\n";
1700 }
1701
1702 // include variables
1703 $message .= "\n\nContext:\n" . print_r($context, true);
1704 $message .= "\n\nGLOBALS:\n" . print_r($GLOBALS, true);
1705 $message .= "\n\nBacktrace:\n" . print_r(debug_backtrace(), true);
1706
1707 // include code fragment
1708 if(file_exists($file))
1709 {
1710 $message .= "\n\n$file:\n";
1711 $code = @file($file);
1712 for($i = max(0, $line - 10); $i < min($line + 10, count($code)); $i++)
1713 $message .= ($i + 1)."\t$code[$i]";
1714 }
1715
1716 // make sure message is fully readable (convert unprintable chars to hex representation)
1717 $ret = '';
1718 for($i = 0; $i < strlen($message); $i++)
1719 {
1720 $c = ord($message{$i});
1721 if($c == 10 || $c == 13 || $c == 9)
1722 $ret .= $message{$i};
1723 elseif($c < 16)
1724 $ret .= '\x0' . dechex($c);
1725 elseif($c < 32 || $c > 127)
1726 $ret .= '\x' . dechex($c);
1727 else
1728 $ret .= $message{$i};
1729 }
1730 $message = $ret;
1731
1732 // send the mail if less than 5 errors
1733 static $mailcount = 0;
1734 if($mailcount < 5)
1735 @mail($phpagi_error_handler_email, $subject, $message);
1736 $mailcount++;
1737 }
1738 }
1739 $phpagi_error_handler_email = NULL;
1740 ?>
Documentation generated on Wed, 25 May 2005 14:28:51 -0600 by phpDocumentor 1.2.3