Search moodle.org's
Developer Documentation

See Release Notes

  • Bug fixes for general core bugs in 3.10.x will end 8 November 2021 (12 months).
  • Bug fixes for security issues in 3.10.x will end 9 May 2022 (18 months).
  • PHP version: minimum PHP 7.2.0 Note: minimum PHP version has increased since Moodle 3.8. PHP 7.3.x and 7.4.x are supported too.

Differences Between: [Versions 39 and 310]

   1  <?php
   2  
   3  namespace BirknerAlex\XMPPHP;
   4  
   5  	 /**
   6  	  * XMPPHP: The PHP XMPP Library
   7  	  * Copyright (C) 2008  Nathanael C. Fritz
   8  	  * This file is part of SleekXMPP.
   9  	  *
  10  	  * XMPPHP is free software; you can redistribute it and/or modify
  11  	  * it under the terms of the GNU General Public License as published by
  12  	  * the Free Software Foundation; either version 2 of the License, or
  13  	  * (at your option) any later version.
  14  	  *
  15  	  * XMPPHP is distributed in the hope that it will be useful,
  16  	  * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17  	  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  18  	  * GNU General Public License for more details.
  19  	  *
  20  	  * You should have received a copy of the GNU General Public License
  21  	  * along with XMPPHP; if not, write to the Free Software
  22  	  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  23  	  *
  24  	  * @category   xmpphp
  25  	  * @package    XMPPHP
  26  	  * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
  27  	  * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
  28  	  * @author     Michael Garvin <JID: gar@netflint.net>
  29  	  * @author     Alexander Birkner (https://github.com/BirknerAlex)
  30  	  * @copyright  2008 Nathanael C. Fritz
  31  	  */
  32  
  33  /**
  34   * XMPPHP Main Class
  35   *
  36   * @category   xmpphp
  37   * @package    XMPPHP
  38   * @author     Nathanael C. Fritz <JID: fritzy@netflint.net>
  39   * @author     Stephan Wentz <JID: stephan@jabber.wentz.it>
  40   * @author     Michael Garvin <JID: gar@netflint.net>
  41   * @copyright  2008 Nathanael C. Fritz
  42   * @version    $Id$
  43   */
  44  class XMLStream {
  45  	 /**
  46  	  * @var resource
  47  	  */
  48  	 protected $socket;
  49  	 /**
  50  	  * @var resource
  51  	  */
  52  	 protected $parser;
  53  	 /**
  54  	  * @var string
  55  	  */
  56  	 protected $buffer;
  57  	 /**
  58  	  * @var integer
  59  	  */
  60  	 protected $xml_depth = 0;
  61  	 /**
  62  	  * @var string
  63  	  */
  64  	 protected $host;
  65  	 /**
  66  	  * @var integer
  67  	  */
  68  	 protected $port;
  69  	 /**
  70  	  * @var string
  71  	  */
  72  	 protected $stream_start = '<stream>';
  73  	 /**
  74  	  * @var string
  75  	  */
  76  	 protected $stream_end = '</stream>';
  77  	 /**
  78  	  * @var boolean
  79  	  */
  80  	 protected $disconnected = true;
  81  	 /**
  82  	  * @var boolean
  83  	  */
  84  	 protected $sent_disconnect = false;
  85  	 /**
  86  	  * @var array
  87  	  */
  88  	 protected $ns_map = array();
  89  	 /**
  90  	  * @var array
  91  	  */
  92  	 protected $current_ns = array();
  93  	 /**
  94  	  * @var array
  95  	  */
  96  	 protected $xmlobj = null;
  97  	 /**
  98  	  * @var array
  99  	  */
 100  	 protected $nshandlers = array();
 101  	 /**
 102  	  * @var array
 103  	  */
 104  	 protected $xpathhandlers = array();
 105  	 /**
 106  	  * @var array
 107  	  */
 108  	 protected $idhandlers = array();
 109  	 /**
 110  	  * @var array
 111  	  */
 112  	 protected $eventhandlers = array();
 113  	 /**
 114  	  * @var integer
 115  	  */
 116  	 protected $lastid = 0;
 117  	 /**
 118  	  * @var string
 119  	  */
 120  	 protected $default_ns;
 121  	 /**
 122  	  * @var string[]
 123  	  */
 124  	 protected $until = array();
 125  	 /**
 126  	  * @var int[]
 127  	  */
 128  	 protected $until_count = array();
 129  	 /**
 130  	  * @var array
 131  	  */
 132  	 protected $until_happened = false;
 133  	 /**
 134  	  * @var array
 135  	  */
 136  	 protected $until_payload = array();
 137  	 /**
 138  	  * @var Log
 139  	  */
 140  	 protected $log;
 141  	 /**
 142  	  * @var boolean
 143  	  */
 144  	 protected $reconnect = true;
 145  	 /**
 146  	  * @var boolean
 147  	  */
 148  	 protected $been_reset = false;
 149  	 /**
 150  	  * @var boolean
 151  	  */
 152  	 protected $is_server;
 153  	 /**
 154  	  * @var float
 155  	  */
 156  	 protected $last_send = 0;
 157  	 /**
 158  	  * @var boolean
 159  	  */
 160  	 protected $use_ssl = false;
 161  	 /**
 162  	  * @var integer
 163  	  */
 164  	 protected $reconnectTimeout = 30;
 165  
 166  	 /**
 167  	  * Constructor
 168  	  *
 169  	  * @param string  $host
 170  	  * @param string  $port
 171  	  * @param boolean $printlog
 172  	  * @param string  $loglevel
 173  	  * @param boolean $is_server
 174  	  */
 175  	public function __construct($host = null, $port = null, $printlog = false, $loglevel = null, $is_server = false) {
 176  	 	 $this->reconnect = !$is_server;
 177  	 	 $this->is_server = $is_server;
 178  	 	 $this->host = $host;
 179  	 	 $this->port = $port;
 180  	 	 $this->setupParser();
 181  	 	 $this->log = new Log($printlog, $loglevel);
 182  	 }
 183  
 184  	 /**
 185  	  * Destructor
 186  	  * Cleanup connection
 187  	  */
 188  	public function __destruct() {
 189  	 	 if(!$this->disconnected && $this->socket) {
 190  	 	 	 $this->disconnect();
 191  	 	 }
 192  	 }
 193  	 
 194  	 /**
 195  	  * Return the log instance
 196  	  *
 197  	  * @return Log
 198  	  */
 199  	public function getLog() {
 200  	 	 return $this->log;
 201  	 }
 202  	 
 203  	 /**
 204  	  * Get next ID
 205  	  *
 206  	  * @return integer
 207  	  */
 208  	public function getId() {
 209  	 	 $this->lastid++;
 210  	 	 return $this->lastid;
 211  	 }
 212  
 213  	 /**
 214  	  * Set SSL
 215  	  *
 216  	  * @return integer
 217  	  */
 218  	public function useSSL($use=true) {
 219  	 	 $this->use_ssl = $use;
 220  	 }
 221  
 222  	 /**
 223  	  * Add ID Handler
 224  	  *
 225  	  * @param integer $id
 226  	  * @param string  $pointer
 227  	  * @param string  $obj
 228  	  */
 229  	public function addIdHandler($id, $pointer, $obj = null) {
 230  	 	 $this->idhandlers[$id] = array($pointer, $obj);
 231  	 }
 232  
 233  	 /**
 234  	  * Add Handler
 235  	  *
 236  	  * @param string $name
 237  	  * @param string  $ns
 238  	  * @param string  $pointer
 239  	  * @param string  $obj
 240  	  * @param integer $depth
 241  	  */
 242  	public function addHandler($name, $ns, $pointer, $obj = null, $depth = 1) {
 243  	 	 #TODO deprication warning
 244  	 	 $this->nshandlers[] = array($name,$ns,$pointer,$obj, $depth);
 245  	 }
 246  
 247  	 /**
 248  	  * Add XPath Handler
 249  	  *
 250  	  * @param string $xpath
 251  	  * @param string $pointer
 252  	  * @param
 253  	  */
 254  	public function addXPathHandler($xpath, $pointer, $obj = null) {
 255  	 	 if (preg_match_all("/\(?{[^\}]+}\)?(\/?)[^\/]+/", $xpath, $regs)) {
 256  	 	 	 $ns_tags = $regs[0];
 257  	 	 } else {
 258  	 	 	 $ns_tags = array($xpath);
 259  	 	 }
 260  	 	 foreach($ns_tags as $ns_tag) {
 261  	 	 	 list($l, $r) = explode('}', $ns_tag);
 262  	 	 	 if ($r != null) {
 263  	 	 	 	 $xpart = array(substr($l, 1), $r);
 264  	 	 	 } else {
 265  	 	 	 	 $xpart = array(null, $l);
 266  	 	 	 }
 267  	 	 	 $xpath_array[] = $xpart;
 268  	 	 }
 269  	 	 $this->xpathhandlers[] = array($xpath_array, $pointer, $obj);
 270  	 }
 271  
 272  	 /**
 273  	  * Add Event Handler
 274  	  *
 275  	  * @param integer $id
 276  	  * @param string  $pointer
 277  	  * @param string  $obj
 278  	  */
 279  	public function addEventHandler($name, $pointer, $obj) {
 280  	 	 $this->eventhandlers[] = array($name, $pointer, $obj);
 281  	 }
 282  
 283  	 /**
 284  	  * Connect to XMPP Host
 285  	  *
 286  	  * @param integer $timeout    Timeout in seconds
 287  	  * @param boolean $persistent
 288  	  * @param boolean $sendinit   Send XMPP starting sequence after connect
 289  	  *                            automatically
 290  	  *
 291  	  * @throws Exception When the connection fails
 292  	  */
 293  	public function connect($timeout = 30, $persistent = false, $sendinit = true) {
 294  	 	 $this->sent_disconnect = false;
 295  	 	 $starttime = time();
 296  	 	 
 297  	 	 do {
 298  	 	 	 $this->disconnected = false;
 299  	 	 	 $this->sent_disconnect = false;
 300  	 	 	 if($persistent) {
 301  	 	 	 	 $conflag = STREAM_CLIENT_CONNECT | STREAM_CLIENT_PERSISTENT;
 302  	 	 	 } else {
 303  	 	 	 	 $conflag = STREAM_CLIENT_CONNECT;
 304  	 	 	 }
 305  	 	 	 $conntype = 'tcp';
 306  	 	 	 if($this->use_ssl) $conntype = 'ssl';
 307  	 	 	 $this->log->log("Connecting to $conntype://{$this->host}:{$this->port}");
 308  	 	 	 try {
 309  	 	 	 	 $this->socket = @stream_socket_client("$conntype://{$this->host}:{$this->port}", $errno, $errstr, $timeout, $conflag);
 310  	 	 	 } catch (Exception $e) {
 311  	 	 	 	 throw new Exception($e->getMessage());
 312  	 	 	 }
 313  	 	 	 if(!$this->socket) {
 314  	 	 	 	 $this->log->log("Could not connect.",  Log::LEVEL_ERROR);
 315  	 	 	 	 $this->disconnected = true;
 316  	 	 	 	 # Take it easy for a few seconds
 317  	 	 	 	 sleep(min($timeout, 5));
 318  	 	 	 }
 319  	 	 } while (!$this->socket && (time() - $starttime) < $timeout);
 320  	 	 
 321  	 	 if ($this->socket) {
 322  	 	 	 stream_set_blocking($this->socket, 1);
 323  	 	 	 if($sendinit) $this->send($this->stream_start);
 324  	 	 } else {
 325  	 	 	 throw new Exception("Could not connect before timeout.");
 326  	 	 }
 327  	 }
 328  
 329  	 /**
 330  	  * Reconnect XMPP Host
 331  	  *
 332  	  * @throws Exception When the connection fails
 333  	  * @uses   $reconnectTimeout
 334  	  * @see    setReconnectTimeout()
 335  	  */
 336  	public function doReconnect() {
 337  	 	 if(!$this->is_server) {
 338  	 	 	 $this->log->log("Reconnecting ($this->reconnectTimeout)...",  Log::LEVEL_WARNING);
 339  	 	 	 $this->connect($this->reconnectTimeout, false, false);
 340  	 	 	 $this->reset();
 341  	 	 	 $this->event('reconnect');
 342  	 	 }
 343  	 }
 344  
 345  	public function setReconnectTimeout($timeout) {
 346  	 	 $this->reconnectTimeout = $timeout;
 347  	 }
 348  	 
 349  	 /**
 350  	  * Disconnect from XMPP Host
 351  	  */
 352  	public function disconnect() {
 353  	 	 $this->log->log("Disconnecting...",  Log::LEVEL_VERBOSE);
 354  	 	 if(false == (bool) $this->socket) {
 355  	 	 	 return;
 356  	 	 }
 357  	 	 $this->reconnect = false;
 358  	 	 $this->send($this->stream_end);
 359  	 	 $this->sent_disconnect = true;
 360  	 	 $this->processUntil('end_stream', 5);
 361  	 	 $this->disconnected = true;
 362  	 }
 363  
 364  	 /**
 365  	  * Are we are disconnected?
 366  	  *
 367  	  * @return boolean
 368  	  */
 369  	public function isDisconnected() {
 370  	 	 return $this->disconnected;
 371  	 }
 372  
 373  	 /**
 374  	  * Checks if the given string is closed with the same tag as it is
 375  	  * opened. We try to be as fast as possible here.
 376  	  *
 377  	  * @param string $buff Read buffer of __process()
 378  	  *
 379  	  * @return boolean true if the buffer seems to be complete
 380  	  */
 381  	protected function bufferComplete($buff)
 382  	 {
 383  	 	 if (substr($buff, -1) != '>') {
 384  	 	 	 return false;
 385  	 	 }
 386  	 	 //we always have a space since the namespace needs to be
 387  	 	 //declared. could be a tab, though
 388  	 	 $start = substr(
 389  	 	 	 $buff, 1,
 390  	 	 	 min(strpos($buff, '>', 2), strpos($buff, ' ', 2)) - 1
 391  	 	 );
 392  	 	 $stop  = substr($buff, -strlen($start) - 3);
 393  
 394  	 	 if ($start == '?xml') {
 395  	 	 	 //starting with an xml tag. this means a stream is being
 396  	 	 	 // opened, which is not much of data, so no fear it's
 397  	 	 	 // not complete
 398  	 	 	 return true;
 399  	 	 }
 400  	 	 if (substr($stop, -2) == '/>') {
 401  	 	 	 //one tag, i.e. <success />
 402  	 	 	 return true;
 403  	 	 }
 404  	 	 if ('</' . $start . '>' == $stop) {
 405  	 	 	 return true;
 406  	 	 }
 407  
 408  	 	 return false;
 409  	 }
 410  
 411  	 /**
 412  	  * Core reading tool
 413  	  *
 414  	  * @param mixed   $maximum Limit when to return
 415  	  *                         - 0: only read if data is immediately ready
 416  	  *                         - NULL: wait forever and ever
 417  	  *                         - integer: process for this amount of microseconds
 418  	  * @param boolean $return_when_received Immediately return when data have been
 419  	  *                                      received
 420  	  *
 421  	  * @return boolean True when all goes well, false when something fails
 422  	  */
 423  	private function __process($maximum = 5, $return_when_received = false)
 424  	 {
 425  	 	 $remaining = $maximum;
 426  	 	 
 427  	 	 do {
 428  	 	 	 $starttime = (microtime(true) * 1000000);
 429  	 	 	 $read = array($this->socket);
 430  	 	 	 $write = array();
 431  	 	 	 $except = array();
 432  	 	 	 if (is_null($maximum)) {
 433  	 	 	 	 $secs = NULL;
 434  	 	 	 	 $usecs = NULL;
 435  	 	 	 } else if ($maximum == 0) {
 436  	 	 	 	 $secs = 0;
 437  	 	 	 	 $usecs = 0;
 438  	 	 	 } else {
 439  	 	 	 	 $usecs = $remaining % 1000000;
 440  	 	 	 	 $secs = floor(($remaining - $usecs) / 1000000);
 441  	 	 	 }
 442  	 	 	 $updated = @stream_select($read, $write, $except, $secs, $usecs);
 443  	 	 	 if ($updated === false) {
 444  	 	 	 	 $this->log->log("Error on stream_select()",  Log::LEVEL_VERBOSE);
 445  	 	 	 	 if ($this->reconnect) {
 446  	 	 	 	 	 $this->doReconnect();
 447  	 	 	 	 } else {
 448  	 	 	 	 	 fclose($this->socket);
 449  	 	 	 	 	 $this->socket = NULL;
 450  	 	 	 	 	 return false;
 451  	 	 	 	 }
 452  	 	 	 } else if ($updated > 0) {
 453  	 	 	 	 $buff = '';
 454  	 	 	 	 do {
 455  	 	 	 	 	 if ($buff != '') {
 456  	 	 	 	 	 	 //disable blocking for now because fread() will
 457  	 	 	 	 	 	 // block until the 4k are full if we already
 458  	 	 	 	 	 	 // read a part of the packet
 459  	 	 	 	 	 	 stream_set_blocking($this->socket, 0);
 460  	 	 	 	 	 }
 461  	 	 	 	 	 $part = fread($this->socket, 4096);
 462  	 	 	 	 	 stream_set_blocking($this->socket, 1);
 463  
 464  	 	 	 	 	 if (!$part && feof($this->socket)) {
 465  	 	 	 	 	 	 if($this->reconnect) {
 466  	 	 	 	 	 	 	 $this->doReconnect();
 467  	 	 	 	 	 	 } else {
 468  	 	 	 	 	 	 	 fclose($this->socket);
 469  	 	 	 	 	 	 	 $this->socket = NULL;
 470  	 	 	 	 	 	 	 return false;
 471  	 	 	 	 	 	 }
 472  	 	 	 	 	 }
 473  	 	 	 	 	 $this->log->log("RECV: $part",  Log::LEVEL_VERBOSE);
 474  	 	 	 	 	 $buff .= $part;
 475  	 	 	 	 } while (!$this->bufferComplete($buff));
 476  
 477  	 	 	 	 xml_parse($this->parser, $buff, false);
 478  	 	 	 	 if ($return_when_received) {
 479  	 	 	 	 	 return true;
 480  	 	 	 	 }
 481  	 	 	 } else {
 482  	 	 	 	 # $updated == 0 means no changes during timeout.
 483  	 	 	 }
 484  	 	 	 $endtime = (microtime(true)*1000000);
 485  	 	 	 $time_past = $endtime - $starttime;
 486  	 	 	 $remaining = $remaining - $time_past;
 487  	 	 } while (is_null($maximum) || $remaining > 0);
 488  	 	 return true;
 489  	 }
 490  	 
 491  	 /**
 492  	  * Process
 493  	  *
 494  	  * @return string
 495  	  */
 496  	public function process() {
 497  	 	 $this->__process(NULL);
 498  	 }
 499  
 500  	 /**
 501  	  * Process until a timeout occurs
 502  	  *
 503  	  * @param integer $timeout Time in seconds
 504  	  *
 505  	  * @return string
 506  	  *
 507  	  * @see __process()
 508  	  */
 509  	public function processTime($timeout=NULL) {
 510  	 	 if (is_null($timeout)) {
 511  	 	 	 return $this->__process(NULL);
 512  	 	 } else {
 513  	 	 	 return $this->__process($timeout * 1000000);
 514  	 	 }
 515  	 }
 516  
 517  	 /**
 518  	  * Process until a specified event or a timeout occurs
 519  	  *
 520  	  * @param string|array $event   Event name or array of event names
 521  	  * @param integer      $timeout Timeout in seconds
 522  	  *
 523  	  * @return array Payload
 524  	  */
 525  	public function processUntil($event, $timeout = -1)
 526  	 {
 527  	 	 if ($this->disconnected) {
 528  	 	 	 throw new Exception('You need to connect first');
 529  	 	 }
 530  
 531  	 	 $start = time();
 532  	 	 if (!is_array($event)) {
 533  	 	 	 $event = array($event);
 534  	 	 }
 535  
 536  	 	 $this->until[] = $event;
 537  	 	 end($this->until);
 538  	 	 $event_key = key($this->until);
 539  	 	 reset($this->until);
 540  
 541  	 	 $this->until_count[$event_key] = 0;
 542  	 	 $updated = '';
 543  	 	 while (!$this->disconnected
 544  	 	 	 && $this->until_count[$event_key] < 1
 545  	 	 	 && ($timeout == -1 || time() - $start < $timeout)
 546  	 	 ) {
 547  	 	 	 $maximum = $timeout == -1
 548  	 	 	 	 ? NULL
 549  	 	 	 	 : ($timeout - (time() - $start)) * 1000000;
 550  	 	 	 $ret = $this->__process($maximum, true);
 551  	 	 	 if (!$ret) {
 552  	 	 	 	 break;
 553  	 	 	 }
 554  	 	 }
 555  
 556  	 	 if (array_key_exists($event_key, $this->until_payload)) {
 557  	 	 	 $payload = $this->until_payload[$event_key];
 558  	 	 	 unset($this->until_payload[$event_key]);
 559  	 	 	 unset($this->until_count[$event_key]);
 560  	 	 	 unset($this->until[$event_key]);
 561  	 	 } else {
 562  	 	 	 $payload = array();
 563  	 	 }
 564  
 565  	 	 return $payload;
 566  	 }
 567  
 568  	 /**
 569  	  * Obsolete?
 570  	  */
 571  	public function Xapply_socket($socket) {
 572  	 	 $this->socket = $socket;
 573  	 }
 574  
 575  	 /**
 576  	  * XML start callback
 577  	  * 
 578  	  * @see xml_set_element_handler
 579  	  *
 580  	  * @param resource $parser
 581  	  * @param string   $name
 582  	  */
 583  	public function startXML($parser, $name, $attr) {
 584  	 	 if($this->been_reset) {
 585  	 	 	 $this->been_reset = false;
 586  	 	 	 $this->xml_depth = 0;
 587  	 	 }
 588  	 	 $this->xml_depth++;
 589  	 	 if(array_key_exists('XMLNS', $attr)) {
 590  	 	 	 $this->current_ns[$this->xml_depth] = $attr['XMLNS'];
 591  	 	 } else {
 592  	 	 	 $this->current_ns[$this->xml_depth] = $this->current_ns[$this->xml_depth - 1];
 593  	 	 	 if(!$this->current_ns[$this->xml_depth]) $this->current_ns[$this->xml_depth] = $this->default_ns;
 594  	 	 }
 595  	 	 $ns = $this->current_ns[$this->xml_depth];
 596  	 	 foreach($attr as $key => $value) {
 597  	 	 	 if(strstr($key, ":")) {
 598  	 	 	 	 $key = explode(':', $key);
 599  	 	 	 	 $key = $key[1];
 600  	 	 	 	 $this->ns_map[$key] = $value;
 601  	 	 	 }
 602  	 	 }
 603  	 	 if(!strstr($name, ":") === false)
 604  	 	 {
 605  	 	 	 $name = explode(':', $name);
 606  	 	 	 $ns = $this->ns_map[$name[0]];
 607  	 	 	 $name = $name[1];
 608  	 	 }
 609  	 	 $obj = new XMLObj($name, $ns, $attr);
 610  	 	 if($this->xml_depth > 1) {
 611  	 	 	 $this->xmlobj[$this->xml_depth - 1]->subs[] = $obj;
 612  	 	 }
 613  	 	 $this->xmlobj[$this->xml_depth] = $obj;
 614  	 }
 615  
 616  	 /**
 617  	  * XML end callback
 618  	  * 
 619  	  * @see xml_set_element_handler
 620  	  *
 621  	  * @param resource $parser
 622  	  * @param string   $name
 623  	  */
 624  	public function endXML($parser, $name) {
 625  	 	 #$this->log->log("Ending $name",  Log::LEVEL_DEBUG);
 626  	 	 #print "$name\n";
 627  	 	 if($this->been_reset) {
 628  	 	 	 $this->been_reset = false;
 629  	 	 	 $this->xml_depth = 0;
 630  	 	 }
 631  	 	 $this->xml_depth--;
 632  	 	 if($this->xml_depth == 1) {
 633  	 	 	 #clean-up old objects
 634  	 	 	 #$found = false; #FIXME This didn't appear to be in use --Gar
 635  	 	 	 foreach($this->xpathhandlers as $handler) {
 636  	 	 	 	 if (is_array($this->xmlobj) && array_key_exists(2, $this->xmlobj)) {
 637  	 	 	 	 	 $searchxml = $this->xmlobj[2];
 638  	 	 	 	 	 $nstag = array_shift($handler[0]);
 639  	 	 	 	 	 if (($nstag[0] == null or $searchxml->ns == $nstag[0]) and ($nstag[1] == "*" or $nstag[1] == $searchxml->name)) {
 640  	 	 	 	 	 	 foreach($handler[0] as $nstag) {
 641  	 	 	 	 	 	 	 if ($searchxml !== null and $searchxml->hasSub($nstag[1], $ns=$nstag[0])) {
 642  	 	 	 	 	 	 	 	 $searchxml = $searchxml->sub($nstag[1], $ns=$nstag[0]);
 643  	 	 	 	 	 	 	 } else {
 644  	 	 	 	 	 	 	 	 $searchxml = null;
 645  	 	 	 	 	 	 	 	 break;
 646  	 	 	 	 	 	 	 }
 647  	 	 	 	 	 	 }
 648  	 	 	 	 	 	 if ($searchxml !== null) {
 649  	 	 	 	 	 	 	 if($handler[2] === null) $handler[2] = $this;
 650  	 	 	 	 	 	 	 $this->log->log("Calling {$handler[1]}",  Log::LEVEL_DEBUG);
 651  	 	 	 	 	 	 	 $handler[2]->{$handler[1]}($this->xmlobj[2]);
 652  	 	 	 	 	 	 }
 653  	 	 	 	 	 }
 654  	 	 	 	 }
 655  	 	 	 }
 656  	 	 	 foreach($this->nshandlers as $handler) {
 657  	 	 	 	 if($handler[4] != 1 and array_key_exists(2, $this->xmlobj) and  $this->xmlobj[2]->hasSub($handler[0])) {
 658  	 	 	 	 	 $searchxml = $this->xmlobj[2]->sub($handler[0]);
 659  	 	 	 	 } elseif(is_array($this->xmlobj) and array_key_exists(2, $this->xmlobj)) {
 660  	 	 	 	 	 $searchxml = $this->xmlobj[2];
 661  	 	 	 	 }
 662  	 	 	 	 if($searchxml !== null and $searchxml->name == $handler[0] and ($searchxml->ns == $handler[1] or (!$handler[1] and $searchxml->ns == $this->default_ns))) {
 663  	 	 	 	 	 if($handler[3] === null) $handler[3] = $this;
 664  	 	 	 	 	 $this->log->log("Calling {$handler[2]}",  Log::LEVEL_DEBUG);
 665  	 	 	 	 	 $handler[3]->{$handler[2]}($this->xmlobj[2]);
 666  	 	 	 	 }
 667  	 	 	 }
 668  	 	 	 foreach($this->idhandlers as $id => $handler) {
 669  	 	 	 	 if(array_key_exists('id', $this->xmlobj[2]->attrs) and $this->xmlobj[2]->attrs['id'] == $id) {
 670  	 	 	 	 	 if($handler[1] === null) $handler[1] = $this;
 671  	 	 	 	 	 $handler[1]->{$handler[0]}($this->xmlobj[2]);
 672  	 	 	 	 	 #id handlers are only used once
 673  	 	 	 	 	 unset($this->idhandlers[$id]);
 674  	 	 	 	 	 break;
 675  	 	 	 	 }
 676  	 	 	 }
 677  	 	 	 if(is_array($this->xmlobj)) {
 678  	 	 	 	 $this->xmlobj = array_slice($this->xmlobj, 0, 1);
 679  	 	 	 	 if(isset($this->xmlobj[0]) && $this->xmlobj[0] instanceof XMLObj) {
 680  	 	 	 	 	 $this->xmlobj[0]->subs = null;
 681  	 	 	 	 }
 682  	 	 	 }
 683  	 	 	 unset($this->xmlobj[2]);
 684  	 	 }
 685  	 	 if($this->xml_depth == 0 and !$this->been_reset) {
 686  	 	 	 if(!$this->disconnected) {
 687  	 	 	 	 if(!$this->sent_disconnect) {
 688  	 	 	 	 	 $this->send($this->stream_end);
 689  	 	 	 	 }
 690  	 	 	 	 $this->disconnected = true;
 691  	 	 	 	 $this->sent_disconnect = true;
 692  	 	 	 	 fclose($this->socket);
 693  	 	 	 	 if($this->reconnect) {
 694  	 	 	 	 	 $this->doReconnect();
 695  	 	 	 	 }
 696  	 	 	 }
 697  	 	 	 $this->event('end_stream');
 698  	 	 }
 699  	 }
 700  
 701  	 /**
 702  	  * XML character callback
 703  	  * @see xml_set_character_data_handler
 704  	  *
 705  	  * @param resource $parser
 706  	  * @param string   $data
 707  	  */
 708  	public function charXML($parser, $data) {
 709  	 	 if(array_key_exists($this->xml_depth, $this->xmlobj)) {
 710  	 	 	 $this->xmlobj[$this->xml_depth]->data .= $data;
 711  	 	 }
 712  	 }
 713  
 714  	 /**
 715  	  * Event?
 716  	  *
 717  	  * @param string $name
 718  	  * @param string $payload
 719  	  */
 720  	public function event($name, $payload = null) {
 721  	 	 $this->log->log("EVENT: $name",  Log::LEVEL_DEBUG);
 722  	 	 foreach($this->eventhandlers as $handler) {
 723  	 	 	 if($name == $handler[0]) {
 724  	 	 	 	 if($handler[2] === null) {
 725  	 	 	 	 	 $handler[2] = $this;
 726  	 	 	 	 }
 727  	 	 	 	 $handler[2]->{$handler[1]}($payload);
 728  	 	 	 }
 729  	 	 }
 730  
 731  	 	 foreach($this->until as $key => $until) {
 732  	 	 	 if(is_array($until)) {
 733  	 	 	 	 if(in_array($name, $until)) {
 734  	 	 	 	 	 $this->until_payload[$key][] = array($name, $payload);
 735  	 	 	 	 	 if(!isset($this->until_count[$key])) {
 736  	 	 	 	 	 	 $this->until_count[$key] = 0;
 737  	 	 	 	 	 }
 738  	 	 	 	 	 $this->until_count[$key] += 1;
 739  	 	 	 	 	 #$this->until[$key] = false;
 740  	 	 	 	 }
 741  	 	 	 }
 742  	 	 }
 743  	 }
 744  
 745  	 /**
 746  	  * Read from socket
 747  	  */
 748  	public function read() {
 749  	 	 $buff = @fread($this->socket, 1024);
 750  	 	 if(!$buff) { 
 751  	 	 	 if($this->reconnect) {
 752  	 	 	 	 $this->doReconnect();
 753  	 	 	 } else {
 754  	 	 	 	 fclose($this->socket);
 755  	 	 	 	 return false;
 756  	 	 	 }
 757  	 	 }
 758  	 	 $this->log->log("RECV: $buff",  Log::LEVEL_VERBOSE);
 759  	 	 xml_parse($this->parser, $buff, false);
 760  	 }
 761  
 762  	 /**
 763  	  * Send to socket
 764  	  *
 765  	  * @param string $msg
 766  	  */
 767  	public function send($msg, $timeout=NULL) {
 768  
 769  	 	 if (is_null($timeout)) {
 770  	 	 	 $secs = NULL;
 771  	 	 	 $usecs = NULL;
 772  	 	 } else if ($timeout == 0) {
 773  	 	 	 $secs = 0;
 774  	 	 	 $usecs = 0;
 775  	 	 } else {
 776  	 	 	 $maximum = $timeout * 1000000;
 777  	 	 	 $usecs = $maximum % 1000000;
 778  	 	 	 $secs = floor(($maximum - $usecs) / 1000000);
 779  	 	 }
 780  	 	 
 781  	 	 $read = array();
 782  	 	 $write = array($this->socket);
 783  	 	 $except = array();
 784  	 	 
 785  	 	 $select = @stream_select($read, $write, $except, $secs, $usecs);
 786  	 	 
 787  	 	 if($select === False) {
 788  	 	 	 $this->log->log("ERROR sending message; reconnecting.");
 789  	 	 	 $this->doReconnect();
 790  	 	 	 # TODO: retry send here
 791  	 	 	 return false;
 792  	 	 } elseif ($select > 0) {
 793  	 	 	 $this->log->log("Socket is ready; send it.", Log::LEVEL_VERBOSE);
 794  	 	 } else {
 795  	 	 	 $this->log->log("Socket is not ready; break.", Log::LEVEL_ERROR);
 796  	 	 	 return false;
 797  	 	 }
 798  	 	 
 799  	 	 $sentbytes = @fwrite($this->socket, $msg);
 800  	 	 $this->log->log("SENT: " . mb_substr($msg, 0, $sentbytes, '8bit'), Log::LEVEL_VERBOSE);
 801  	 	 if($sentbytes === FALSE) {
 802  	 	 	 $this->log->log("ERROR sending message; reconnecting.", Log::LEVEL_ERROR);
 803  	 	 	 $this->doReconnect();
 804  	 	 	 return false;
 805  	 	 }
 806  	 	 $this->log->log("Successfully sent $sentbytes bytes.", Log::LEVEL_VERBOSE);
 807  	 	 return $sentbytes;
 808  	 }
 809  
 810  	public function time() {
 811  	 	 list($usec, $sec) = explode(" ", microtime());
 812  	 	 return (float)$sec + (float)$usec;
 813  	 }
 814  
 815  	 /**
 816  	  * Reset connection
 817  	  */
 818  	public function reset() {
 819  	 	 $this->xml_depth = 0;
 820  	 	 unset($this->xmlobj);
 821  	 	 $this->xmlobj = array();
 822  	 	 $this->setupParser();
 823  	 	 if(!$this->is_server) {
 824  	 	 	 $this->send($this->stream_start);
 825  	 	 }
 826  	 	 $this->been_reset = true;
 827  	 }
 828  
 829  	 /**
 830  	  * Setup the XML parser
 831  	  */
 832  	public function setupParser() {
 833  	 	 $this->parser = xml_parser_create('UTF-8');
 834  	 	 xml_parser_set_option($this->parser, XML_OPTION_SKIP_WHITE, 1);
 835  	 	 xml_parser_set_option($this->parser, XML_OPTION_TARGET_ENCODING, 'UTF-8');
 836  	 	 xml_set_object($this->parser, $this);
 837  	 	 xml_set_element_handler($this->parser, 'startXML', 'endXML');
 838  	 	 xml_set_character_data_handler($this->parser, 'charXML');
 839  	 }
 840  
 841  	public function readyToProcess() {
 842  	 	 $read = array($this->socket);
 843  	 	 $write = array();
 844  	 	 $except = array();
 845  	 	 $updated = @stream_select($read, $write, $except, 0);
 846  	 	 return (($updated !== false) && ($updated > 0));
 847  	 }
 848  }