XMLStream.php 23 KB

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