1<?php
2
3/* libyate.php
4 * This file is part of the YATE Project http://YATE.null.ro
5 *
6 * PHP object-oriented interface library for Yate
7 *
8 * Yet Another Telephony Engine - a fully featured software PBX and IVR
9 * Copyright (C) 2004-2014 Null Team
10 *
11 * This software is distributed under multiple licenses;
12 * see the COPYING file in the main directory for licensing
13 * information for this specific distribution.
14 *
15 * This use of this software may be subject to additional restrictions.
16 * See the LEGAL file in the main directory for details.
17 *
18 * This program is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 */
22
23/*
24    WARNING: This file is for PHP 5
25    To modify it for PHP 4 use the following command (needs sed version 4)
26
27    sed -i.bak -e 's/static \(function\)/\1/' libyate.php
28*/
29
30/**
31 * The Yate class encapsulates the object oriented interface of PHP to Yate
32 */
33class Yate
34{
35    /** String: Type of the event */
36    var $type;
37    /** String: Name of the message */
38    var $name;
39    /** String: Return value of the message */
40    var $retval;
41    /** Number: Time the message was generated */
42    var $origin;
43    /** String: Temporarily unique message ID */
44    var $id;
45    /** Boolean: Was the message handled or not */
46    var $handled;
47    /** Array: Message parameters, $obj->params["name"]="value" */
48    var $params;
49
50    /**
51     * Static function to output a string to Yate's stderr or logfile
52     * NOTE: Yate expects an escaped string for 'debug'
53     * @param $str String to output
54     * @param $level Debug level. Send a debug message if greater than 0
55     * @param $escape Escape the string to output
56     */
57    static function Output($str,$level = 0,$escape = false)
58    {
59	global $yate_stderr, $yate_output;
60	if ($str === true)
61	    $yate_output = true;
62	else if ($str === false)
63	    $yate_output = false;
64	else if ($yate_output) {
65	    if ($escape)
66		$str = Yate::Escape($str);
67	    if ($level <= 0)
68		_yate_print("%%>output:$str\n");
69	    else
70		_yate_print("%%>debug:$level:$str\n");
71	}
72	else
73	    fputs($yate_stderr, "$str\n");
74    }
75
76    /**
77     * Static function to output a string to Yate's stderr or logfile
78     *  only if debugging was enabled.
79     * @param $str String to output, or boolean (true/false) to set debugging
80     * @param $level Debug level. Send a debug message if greater than 0
81     * @param $escape Escape the string to output
82     */
83    static function Debug($str,$level = 0,$escape = false)
84    {
85	global $yate_stderr, $yate_debug;
86	if ($str === true)
87	    $yate_debug = true;
88	else if ($str === false)
89	    $yate_debug = false;
90	else if ($yate_debug)
91	    Yate::Output($str,$level,$escape);
92    }
93
94    /**
95     * Static function to get the unique argument passed by Yate at start time
96     * @return First (and only) command line argument passed by Yate
97     */
98    static function Arg()
99    {
100	if (isset($_SERVER['argv'][1]))
101	    return $_SERVER['argv'][1];
102	return null;
103    }
104
105    /**
106     * Static function to convert a Yate string representation to a boolean
107     * @param $str String value to convert
108     * @return True if $str is "true", false otherwise
109     */
110    static function Str2bool($str)
111    {
112	return ($str == "true") ? true : false;
113    }
114
115    /**
116     * Static function to convert a boolean to a Yate string representation
117     * @param $bool Boolean value to convert
118     * @return The string "true" if $bool was true, "false" otherwise
119     */
120    static function Bool2str($bool)
121    {
122	return $bool ? "true" : "false";
123    }
124
125    /**
126     * Static function to convert a string to its Yate escaped format
127     * @param $str String to escape
128     * @param $extra (optional) Character to escape in addition to required ones
129     * @return Yate escaped string
130     */
131    static function Escape($str, $extra = "")
132    {
133	if ($str === true)
134	    return "true";
135	if ($str === false)
136	    return "false";
137	$str = $str . "";
138	$s = "";
139	$n = strlen($str);
140	for ($i=0; $i<$n; $i++) {
141	    $c = $str{$i};
142	    if ((ord($c) < 32) || ($c == ':') || ($c == $extra)) {
143		$c = chr(ord($c) + 64);
144		$s .= '%';
145	    }
146	    else if ($c == '%')
147		$s .= $c;
148	    $s .= $c;
149	}
150	return $s;
151    }
152
153    /**
154     * Static function to convert a Yate escaped string back to a plain string
155     * @param $str Yate escaped string to unescape
156     * @return Unescaped string
157     */
158    static function Unescape($str)
159    {
160	$s = "";
161	$n = strlen($str);
162	for ($i=0; $i<$n; $i++) {
163	    $c = $str{$i};
164	    if ($c == '%') {
165		$i++;
166		$c = $str{$i};
167		if ($c != '%')
168		    $c = chr(ord($c) - 64);
169	    }
170	    $s .= $c;
171	}
172	return $s;
173    }
174
175    /**
176     * Install a Yate message handler
177     * @param $name Name of the messages to handle
178     * @param $priority (optional) Priority to insert in chain, default 100
179     * @param $filtname (optional) Name of parameter to filter for
180     * @param $filtvalue (optional) Matching value of filtered parameter
181     */
182    static function Install($name, $priority = 100, $filtname = "", $filtvalue = "")
183    {
184	$name=Yate::Escape($name);
185	if ($filtname)
186	    $filtname=":$filtname:$filtvalue";
187	_yate_print("%%>install:$priority:$name$filtname\n");
188    }
189
190    /**
191     * Uninstall a Yate message handler
192     * @param $name Name of the messages to stop handling
193     */
194    static function Uninstall($name)
195    {
196	$name=Yate::Escape($name);
197	_yate_print("%%>uninstall:$name\n");
198    }
199
200    /**
201     * Install a Yate message watcher
202     * @param $name Name of the messages to watch
203     */
204    static function Watch($name)
205    {
206	$name=Yate::Escape($name);
207	_yate_print("%%>watch:$name\n");
208    }
209
210    /**
211     * Uninstall a Yate message watcher
212     * @param $name Name of the messages to stop watching
213     */
214    static function Unwatch($name)
215    {
216	$name=Yate::Escape($name);
217	_yate_print("%%>unwatch:$name\n");
218    }
219
220    /**
221     * Changes a local module parameter
222     * @param $name Name of the parameter to modify
223     * @param $value New value to set in the parameter
224     */
225    static function SetLocal($name, $value)
226    {
227	if (($value === true) || ($value === false))
228	    $value = Yate::Bool2str($value);
229	$name=Yate::Escape($name);
230	$value=Yate::Escape($value);
231	_yate_print("%%>setlocal:$name:$value\n");
232    }
233
234    /**
235     * Asynchronously retrieve a local module parameter
236     * @param $name Name of the parameter to read
237     */
238    static function GetLocal($name)
239    {
240	Yate::SetLocal($name, "");
241    }
242
243    /**
244     * Constructor. Creates a new outgoing message
245     * @param $name Name of the new message
246     * @param $retval (optional) Default return
247     * @param $id (optional) Identifier of the new message
248     */
249    function __construct($name, $retval = "", $id = "")
250    {
251	if ($id == "")
252	    $id=uniqid(rand(),1);
253	$this->type="outgoing";
254	$this->name=$name;
255	$this->retval=$retval;
256	$this->origin=time();
257	$this->handled=false;
258	$this->id=$id;
259	$this->params=array();
260    }
261
262    /**
263     * Retrieve the value of a named parameter
264     * @param $key Name of the parameter to retrieve
265     * @param $defvalue (optional) Default value to return if parameter is not set
266     * @return Value of the $key parameter or $defvalue
267     */
268    function GetValue($key, $defvalue = null)
269    {
270	if (isset($this->params[$key]))
271	    return $this->params[$key];
272	return $defvalue;
273    }
274
275    /**
276     * Set a named parameter
277     * @param $key Name of the parameter to set
278     * @param $value Value to set in the parameter
279     */
280    function SetParam($key, $value)
281    {
282	if (($value === true) || ($value === false))
283	    $value = Yate::Bool2str($value);
284	$this->params[$key] = $value;
285    }
286
287    /**
288     * Fill the parameter array from a text representation
289     * @param $parts A numerically indexed array with the key=value parameters
290     * @param $offs (optional) Offset in array to start processing from
291     */
292    function FillParams(&$parts, $offs = 0)
293    {
294	$n = count($parts);
295	for ($i=$offs; $i<$n; $i++) {
296	    $s=$parts[$i];
297	    $q=strpos($s,'=');
298	    if ($q === false)
299		$this->params[Yate::Unescape($s)]=NULL;
300	    else
301		$this->params[Yate::Unescape(substr($s,0,$q))]=Yate::Unescape(substr($s,$q+1));
302	}
303    }
304
305    /**
306     * Dispatch the message to Yate for handling
307     * @param $message Message object to dispatch
308     */
309    function Dispatch()
310    {
311	if ($this->type != "outgoing") {
312	    Yate::Output("PHP bug: attempt to dispatch message type: " . $this->type);
313	    return;
314	}
315	$i=Yate::Escape($this->id);
316	$t=(int)$this->origin;
317	$n=Yate::Escape($this->name);
318	$r=Yate::Escape($this->retval);
319	$p="";
320	$pa = array(&$p);
321	array_walk($this->params, "_yate_message_walk", $pa);
322	_yate_print("%%>message:$i:$t:$n:$r$p\n");
323	$this->type="dispatched";
324    }
325
326    /**
327     * Acknowledge the processing of a message passed from Yate
328     * @param $handled (optional) True if Yate should stop processing, default false
329     */
330    function Acknowledge()
331    {
332	if ($this->type != "incoming") {
333	    Yate::Output("PHP bug: attempt to acknowledge message type: " . $this->type);
334	    return;
335	}
336	$i=Yate::Escape($this->id);
337	$k=Yate::Bool2str($this->handled);
338	$n=Yate::Escape($this->name);
339	$r=Yate::Escape($this->retval);
340	$p="";
341	$pa = array(&$p);
342	array_walk($this->params, "_yate_message_walk", $pa);
343	_yate_print("%%<message:$i:$k:$n:$r$p\n");
344	$this->type="acknowledged";
345    }
346
347    /**
348     * This static function processes just one input line.
349     * It must be called in a loop to keep messages running. Or else.
350     * @return false if we should exit, true if we should keep running,
351     *  or an Yate object instance. Remember to use === and !== operators
352     *  when comparing against true and false.
353     */
354    static function GetEvent()
355    {
356	global $yate_stdin, $yate_socket, $yate_buffer;
357	if ($yate_socket) {
358	    $line = @socket_read($yate_socket,$yate_buffer,PHP_NORMAL_READ);
359	    // check for error
360	    if ($line == false) {
361		switch (socket_last_error($yate_socket)) {
362		    case 4:  // EINTR
363		    case 11: // EAGAIN
364			return true;
365		}
366		return false;
367	    }
368	    // check for EOF
369	    if ($line === "")
370		return false;
371	}
372	else {
373	    if ($yate_stdin == false)
374		return false;
375	    // check for EOF
376	    if (feof($yate_stdin))
377		return false;
378	    $line=fgets($yate_stdin,$yate_buffer);
379	    // check for async read no data
380	    if ($line == false)
381		return true;
382	}
383	$line=str_replace("\n", "", $line);
384	if ($line == "")
385	    return true;
386	$ev=true;
387	$part=explode(":", $line);
388	switch ($part[0]) {
389	    case "%%>message":
390		/* incoming message str_id:int_time:str_name:str_retval[:key=value...] */
391		$ev=new Yate(Yate::Unescape($part[3]),Yate::Unescape($part[4]),Yate::Unescape($part[1]));
392		$ev->type="incoming";
393		$ev->origin=(int)$part[2];
394		$ev->FillParams($part,5);
395		break;
396	    case "%%<message":
397		/* message answer str_id:bool_handled:str_name:str_retval[:key=value...] */
398		$ev=new Yate(Yate::Unescape($part[3]),Yate::Unescape($part[4]),Yate::Unescape($part[1]));
399		$ev->type="answer";
400		$ev->handled=Yate::Str2bool($part[2]);
401		$ev->FillParams($part,5);
402		break;
403	    case "%%<install":
404		/* install answer num_priority:str_name:bool_success */
405		$ev=new Yate(Yate::Unescape($part[2]),"",(int)$part[1]);
406		$ev->type="installed";
407		$ev->handled=Yate::Str2bool($part[3]);
408		break;
409	    case "%%<uninstall":
410		/* uninstall answer num_priority:str_name:bool_success */
411		$ev=new Yate(Yate::Unescape($part[2]),"",(int)$part[1]);
412		$ev->type="uninstalled";
413		$ev->handled=Yate::Str2bool($part[3]);
414		break;
415	    case "%%<watch":
416		/* watch answer str_name:bool_success */
417		$ev=new Yate(Yate::Unescape($part[1]));
418		$ev->type="watched";
419		$ev->handled=Yate::Str2bool($part[2]);
420		break;
421	    case "%%<unwatch":
422		/* unwatch answer str_name:bool_success */
423		$ev=new Yate(Yate::Unescape($part[1]));
424		$ev->type="unwatched";
425		$ev->handled=Yate::Str2bool($part[2]);
426		break;
427	    case "%%<connect":
428		/* connect answer str_role:bool_success */
429		$ev=new Yate(Yate::Unescape($part[1]));
430		$ev->type="connected";
431		$ev->handled=Yate::Str2bool($part[2]);
432		break;
433	    case "%%<setlocal":
434		/* local parameter answer str_name:str_value:bool_success */
435		$ev=new Yate(Yate::Unescape($part[1]),Yate::Unescape($part[2]));
436		$ev->type="setlocal";
437		$ev->handled=Yate::Str2bool($part[3]);
438		break;
439	    case "%%<quit":
440		if ($yate_socket) {
441		    socket_close($yate_socket);
442		    $yate_socket = false;
443		}
444		else if ($yate_stdin) {
445		    fclose($yate_stdin);
446		    $yate_stdin = false;
447		}
448		return false;
449	    case "Error in":
450		/* We are already in error so better stay quiet */
451		break;
452	    default:
453		Yate::Output("PHP parse error: " . $line);
454	}
455	return $ev;
456    }
457
458    /**
459     * This static function initializes globals in the PHP Yate External Module.
460     * It should be called before any other method.
461     * @param $async (optional) True if asynchronous, polled mode is desired
462     * @param $addr Hostname to connect to or UNIX socket path
463     * @param $port TCP port to connect to, zero to use UNIX sockets
464     * @param $role Role of this connection - "global" or "channel"
465     * @return True if initialization succeeded, false if failed
466     */
467    static function Init($async = false, $addr = "", $port = 0, $role = "", $buffer = 8192)
468    {
469	global $yate_stdin, $yate_stdout, $yate_stderr;
470	global $yate_socket, $yate_buffer, $yate_debug, $yate_output;
471	$yate_debug = false;
472	$yate_stdin = false;
473	$yate_stdout = false;
474	$yate_stderr = false;
475	$yate_output = false;
476	$yate_socket = false;
477	if ($buffer < 2048)
478	    $buffer = 2048;
479	else if ($buffer > 65536)
480	    $buffer = 65536;
481	$yate_buffer = $buffer;
482	if ($addr) {
483	    $ok = false;
484	    if (!function_exists("socket_create")) {
485		$yate_stderr = fopen("php://stderr","w");
486		Yate::Output("PHP sockets missing, initialization failed");
487		return false;
488	    }
489	    if ($port) {
490		$yate_socket = @socket_create(AF_INET,SOCK_STREAM,SOL_TCP);
491		$ok = @socket_connect($yate_socket,$addr,$port);
492	    }
493	    else {
494		$yate_socket = @socket_create(AF_UNIX,SOCK_STREAM,0);
495		$ok = @socket_connect($yate_socket,$addr,$port);
496	    }
497	    if (($yate_socket === false) || !$ok) {
498		$yate_socket = false;
499		$yate_stderr = fopen("php://stderr","w");
500		Yate::Output("Socket error, initialization failed");
501		return false;
502	    }
503	    $yate_output = true;
504	}
505	else {
506	    $yate_stdin = fopen("php://stdin","r");
507	    $yate_stdout = fopen("php://stdout","w");
508	    $yate_stderr = fopen("php://stderr","w");
509	    $role = "";
510	    flush();
511	}
512	set_error_handler("_yate_error_handler");
513	ob_implicit_flush(1);
514	if ($async && function_exists("stream_set_blocking") && $yate_stdin)
515	    stream_set_blocking($yate_stdin,false);
516	if ($role)
517	    _yate_print("%%>connect:$role\n");
518	return true;
519    }
520
521    /**
522     * Send a quit command to the External Module
523     * @param close Set to true to close the communication immediately
524     */
525    static function Quit($close = false)
526    {
527	global $yate_stdin, $yate_socket;
528	_yate_print("%%>quit\n");
529	if ($close) {
530	    if ($yate_socket) {
531		socket_close($yate_socket);
532		$yate_socket = false;
533	    }
534	    else if ($yate_stdin) {
535		fclose($yate_stdin);
536		$yate_stdin = false;
537	    }
538	}
539    }
540
541}
542
543/* Internal error handler callback - output plain text to stderr */
544function _yate_error_handler($errno, $errstr, $errfile, $errline)
545{
546    if (0 === error_reporting())
547	return;
548    $str = "[$errno] $errstr in $errfile line $errline\n";
549    switch ($errno) {
550	case E_USER_ERROR:
551	    Yate::Output("PHP fatal: $str");
552	    exit(1);
553	    break;
554	case E_WARNING:
555	case E_USER_WARNING:
556	    Yate::Output("PHP error: $str");
557	    break;
558	case E_NOTICE:
559	case E_USER_NOTICE:
560	    Yate::Output("PHP warning: $str");
561	    break;
562	default:
563	    Yate::Output("PHP unknown error: $str");
564    }
565}
566
567/* Internal function */
568function _yate_print($str)
569{
570    global $yate_stdout, $yate_socket;
571    $max = 10;
572    while ("" != $str) {
573	if ($max-- <= 0)
574	    return;
575	// handle partial writes and check errors
576	if ($yate_socket) {
577	    $w = @socket_write($yate_socket, $str);
578	    if (false === $w) {
579		switch (socket_last_error($yate_socket)) {
580		    case 4:  // EINTR
581		    case 11: // EAGAIN
582			break;
583		    default:
584			return;
585		}
586	    }
587	}
588	else if ($yate_stdout)
589	    $w = fputs($yate_stdout, $str);
590	else
591	    return;
592	if ($w) {
593	    $str = substr($str,$w);
594	    if (false === $str)
595		return;
596	}
597	else
598	    usleep(1000);
599    }
600}
601
602/* Internal function */
603function _yate_message_walk($item, $key, &$result)
604{
605    // workaround to pass by reference the 3rd parameter to message_walk:
606    // the reference is placed in an array and the array is passed by value
607    // taken from: http://hellsgate.online.ee/~glen/array_walk2.php
608    $result[0] .= ':' . Yate::Escape($key,'=') . '=' . Yate::Escape($item);
609}
610
611/* vi: set ts=8 sw=4 sts=4 noet: */
612?>
613