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