PageRenderTime 51ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/includes/functions_jabber.php

https://bitbucket.org/jablonski/yebood
PHP | 873 lines | 558 code | 128 blank | 187 comment | 96 complexity | d356ae3514932e3f42d253380d1ce62f MD5 | raw file
Possible License(s): AGPL-1.0
  1. <?php
  2. /**
  3. *
  4. * @package phpBB3
  5. * @version $Id$
  6. * @copyright (c) 2007 phpBB Group
  7. * @license http://opensource.org/licenses/gpl-license.php GNU Public License
  8. *
  9. */
  10. /**
  11. * @ignore
  12. */
  13. if (!defined('IN_PHPBB'))
  14. {
  15. exit;
  16. }
  17. /**
  18. *
  19. * Jabber class from Flyspray project
  20. *
  21. * @version class.jabber2.php 1595 2008-09-19 (0.9.9)
  22. * @copyright 2006 Flyspray.org
  23. * @author Florian Schmitz (floele)
  24. *
  25. * Only slightly modified by Acyd Burn
  26. *
  27. * @package phpBB3
  28. */
  29. class jabber
  30. {
  31. var $connection = null;
  32. var $session = array();
  33. var $timeout = 10;
  34. var $server;
  35. var $connect_server;
  36. var $port;
  37. var $username;
  38. var $password;
  39. var $use_ssl;
  40. var $resource = 'functions_jabber.phpbb.php';
  41. var $enable_logging;
  42. var $log_array;
  43. var $features = array();
  44. /**
  45. */
  46. function jabber($server, $port, $username, $password, $use_ssl = false)
  47. {
  48. $this->connect_server = ($server) ? $server : 'localhost';
  49. $this->port = ($port) ? $port : 5222;
  50. // Get the server and the username
  51. if (strpos($username, '@') === false)
  52. {
  53. $this->server = $this->connect_server;
  54. $this->username = $username;
  55. }
  56. else
  57. {
  58. $jid = explode('@', $username, 2);
  59. $this->username = $jid[0];
  60. $this->server = $jid[1];
  61. }
  62. $this->password = $password;
  63. $this->use_ssl = ($use_ssl && $this->can_use_ssl()) ? true : false;
  64. // Change port if we use SSL
  65. if ($this->port == 5222 && $this->use_ssl)
  66. {
  67. $this->port = 5223;
  68. }
  69. $this->enable_logging = true;
  70. $this->log_array = array();
  71. }
  72. /**
  73. * Able to use the SSL functionality?
  74. */
  75. function can_use_ssl()
  76. {
  77. // Will not work with PHP >= 5.2.1 or < 5.2.3RC2 until timeout problem with ssl hasn't been fixed (http://bugs.php.net/41236)
  78. return ((version_compare(PHP_VERSION, '5.2.1', '<') || version_compare(PHP_VERSION, '5.2.3RC2', '>=')) && @extension_loaded('openssl')) ? true : false;
  79. }
  80. /**
  81. * Able to use TLS?
  82. */
  83. function can_use_tls()
  84. {
  85. if (!@extension_loaded('openssl') || !function_exists('stream_socket_enable_crypto') || !function_exists('stream_get_meta_data') || !function_exists('socket_set_blocking') || !function_exists('stream_get_wrappers'))
  86. {
  87. return false;
  88. }
  89. /**
  90. * Make sure the encryption stream is supported
  91. * Also seem to work without the crypto stream if correctly compiled
  92. $streams = stream_get_wrappers();
  93. if (!in_array('streams.crypto', $streams))
  94. {
  95. return false;
  96. }
  97. */
  98. return true;
  99. }
  100. /**
  101. * Sets the resource which is used. No validation is done here, only escaping.
  102. * @param string $name
  103. * @access public
  104. */
  105. function set_resource($name)
  106. {
  107. $this->resource = $name;
  108. }
  109. /**
  110. * Connect
  111. */
  112. function connect()
  113. {
  114. /* if (!$this->check_jid($this->username . '@' . $this->server))
  115. {
  116. $this->add_to_log('Error: Jabber ID is not valid: ' . $this->username . '@' . $this->server);
  117. return false;
  118. }*/
  119. $this->session['ssl'] = $this->use_ssl;
  120. if ($this->open_socket($this->connect_server, $this->port, $this->use_ssl))
  121. {
  122. $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
  123. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  124. }
  125. else
  126. {
  127. $this->add_to_log('Error: connect() #2');
  128. return false;
  129. }
  130. // Now we listen what the server has to say...and give appropriate responses
  131. $this->response($this->listen());
  132. return true;
  133. }
  134. /**
  135. * Disconnect
  136. */
  137. function disconnect()
  138. {
  139. if ($this->connected())
  140. {
  141. // disconnect gracefully
  142. if (isset($this->session['sent_presence']))
  143. {
  144. $this->send_presence('offline', '', true);
  145. }
  146. $this->send('</stream:stream>');
  147. $this->session = array();
  148. return fclose($this->connection);
  149. }
  150. return false;
  151. }
  152. /**
  153. * Connected?
  154. */
  155. function connected()
  156. {
  157. return (is_resource($this->connection) && !feof($this->connection)) ? true : false;
  158. }
  159. /**
  160. * Initiates login (using data from contructor, after calling connect())
  161. * @access public
  162. * @return bool
  163. */
  164. function login()
  165. {
  166. if (!sizeof($this->features))
  167. {
  168. $this->add_to_log('Error: No feature information from server available.');
  169. return false;
  170. }
  171. return $this->response($this->features);
  172. }
  173. /**
  174. * Send data to the Jabber server
  175. * @param string $xml
  176. * @access public
  177. * @return bool
  178. */
  179. function send($xml)
  180. {
  181. if ($this->connected())
  182. {
  183. $xml = trim($xml);
  184. $this->add_to_log('SEND: '. $xml);
  185. return fwrite($this->connection, $xml);
  186. }
  187. else
  188. {
  189. $this->add_to_log('Error: Could not send, connection lost (flood?).');
  190. return false;
  191. }
  192. }
  193. /**
  194. * OpenSocket
  195. * @param string $server host to connect to
  196. * @param int $port port number
  197. * @param bool $use_ssl use ssl or not
  198. * @access public
  199. * @return bool
  200. */
  201. function open_socket($server, $port, $use_ssl = false)
  202. {
  203. if (@function_exists('dns_get_record'))
  204. {
  205. $record = @dns_get_record("_xmpp-client._tcp.$server", DNS_SRV);
  206. if (!empty($record) && !empty($record[0]['target']))
  207. {
  208. $server = $record[0]['target'];
  209. }
  210. }
  211. $server = $use_ssl ? 'ssl://' . $server : $server;
  212. if ($this->connection = @fsockopen($server, $port, $errorno, $errorstr, $this->timeout))
  213. {
  214. socket_set_blocking($this->connection, 0);
  215. socket_set_timeout($this->connection, 60);
  216. return true;
  217. }
  218. // Apparently an error occured...
  219. $this->add_to_log('Error: open_socket() - ' . $errorstr);
  220. return false;
  221. }
  222. /**
  223. * Return log
  224. */
  225. function get_log()
  226. {
  227. if ($this->enable_logging && sizeof($this->log_array))
  228. {
  229. return implode("<br /><br />", $this->log_array);
  230. }
  231. return '';
  232. }
  233. /**
  234. * Add information to log
  235. */
  236. function add_to_log($string)
  237. {
  238. if ($this->enable_logging)
  239. {
  240. $this->log_array[] = utf8_htmlspecialchars($string);
  241. }
  242. }
  243. /**
  244. * Listens to the connection until it gets data or the timeout is reached.
  245. * Thus, it should only be called if data is expected to be received.
  246. * @access public
  247. * @return mixed either false for timeout or an array with the received data
  248. */
  249. function listen($timeout = 10, $wait = false)
  250. {
  251. if (!$this->connected())
  252. {
  253. return false;
  254. }
  255. // Wait for a response until timeout is reached
  256. $start = time();
  257. $data = '';
  258. do
  259. {
  260. $read = trim(fread($this->connection, 4096));
  261. $data .= $read;
  262. }
  263. while (time() <= $start + $timeout && !feof($this->connection) && ($wait || $data == '' || $read != '' || (substr(rtrim($data), -1) != '>')));
  264. if ($data != '')
  265. {
  266. $this->add_to_log('RECV: '. $data);
  267. return $this->xmlize($data);
  268. }
  269. else
  270. {
  271. $this->add_to_log('Timeout, no response from server.');
  272. return false;
  273. }
  274. }
  275. /**
  276. * Initiates account registration (based on data used for contructor)
  277. * @access public
  278. * @return bool
  279. */
  280. function register()
  281. {
  282. if (!isset($this->session['id']) || isset($this->session['jid']))
  283. {
  284. $this->add_to_log('Error: Cannot initiate registration.');
  285. return false;
  286. }
  287. $this->send("<iq type='get' id='reg_1'><query xmlns='jabber:iq:register'/></iq>");
  288. return $this->response($this->listen());
  289. }
  290. /**
  291. * Sets account presence. No additional info required (default is "online" status)
  292. * @param $message online, offline...
  293. * @param $type dnd, away, chat, xa or nothing
  294. * @param $unavailable set this to true if you want to become unavailable
  295. * @access public
  296. * @return bool
  297. */
  298. function send_presence($message = '', $type = '', $unavailable = false)
  299. {
  300. if (!isset($this->session['jid']))
  301. {
  302. $this->add_to_log('ERROR: send_presence() - Cannot set presence at this point, no jid given.');
  303. return false;
  304. }
  305. $type = strtolower($type);
  306. $type = (in_array($type, array('dnd', 'away', 'chat', 'xa'))) ? '<show>'. $type .'</show>' : '';
  307. $unavailable = ($unavailable) ? " type='unavailable'" : '';
  308. $message = ($message) ? '<status>' . utf8_htmlspecialchars($message) .'</status>' : '';
  309. $this->session['sent_presence'] = !$unavailable;
  310. return $this->send("<presence$unavailable>" . $type . $message . '</presence>');
  311. }
  312. /**
  313. * This handles all the different XML elements
  314. * @param array $xml
  315. * @access public
  316. * @return bool
  317. */
  318. function response($xml)
  319. {
  320. if (!is_array($xml) || !sizeof($xml))
  321. {
  322. return false;
  323. }
  324. // did we get multiple elements? do one after another
  325. // array('message' => ..., 'presence' => ...)
  326. if (sizeof($xml) > 1)
  327. {
  328. foreach ($xml as $key => $value)
  329. {
  330. $this->response(array($key => $value));
  331. }
  332. return;
  333. }
  334. else
  335. {
  336. // or even multiple elements of the same type?
  337. // array('message' => array(0 => ..., 1 => ...))
  338. if (sizeof(reset($xml)) > 1)
  339. {
  340. foreach (reset($xml) as $value)
  341. {
  342. $this->response(array(key($xml) => array(0 => $value)));
  343. }
  344. return;
  345. }
  346. }
  347. switch (key($xml))
  348. {
  349. case 'stream:stream':
  350. // Connection initialised (or after authentication). Not much to do here...
  351. if (isset($xml['stream:stream'][0]['#']['stream:features']))
  352. {
  353. // we already got all info we need
  354. $this->features = $xml['stream:stream'][0]['#'];
  355. }
  356. else
  357. {
  358. $this->features = $this->listen();
  359. }
  360. $second_time = isset($this->session['id']);
  361. $this->session['id'] = $xml['stream:stream'][0]['@']['id'];
  362. if ($second_time)
  363. {
  364. // If we are here for the second time after TLS, we need to continue logging in
  365. return $this->login();
  366. }
  367. // go on with authentication?
  368. if (isset($this->features['stream:features'][0]['#']['bind']) || !empty($this->session['tls']))
  369. {
  370. return $this->response($this->features);
  371. }
  372. break;
  373. case 'stream:features':
  374. // Resource binding after successful authentication
  375. if (isset($this->session['authenticated']))
  376. {
  377. // session required?
  378. $this->session['sess_required'] = isset($xml['stream:features'][0]['#']['session']);
  379. $this->send("<iq type='set' id='bind_1'>
  380. <bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
  381. <resource>" . utf8_htmlspecialchars($this->resource) . '</resource>
  382. </bind>
  383. </iq>');
  384. return $this->response($this->listen());
  385. }
  386. // Let's use TLS if SSL is not enabled and we can actually use it
  387. if (!$this->session['ssl'] && $this->can_use_tls() && $this->can_use_ssl() && isset($xml['stream:features'][0]['#']['starttls']))
  388. {
  389. $this->add_to_log('Switching to TLS.');
  390. $this->send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n");
  391. return $this->response($this->listen());
  392. }
  393. // Does the server support SASL authentication?
  394. // I hope so, because we do (and no other method).
  395. if (isset($xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns']) && $xml['stream:features'][0]['#']['mechanisms'][0]['@']['xmlns'] == 'urn:ietf:params:xml:ns:xmpp-sasl')
  396. {
  397. // Now decide on method
  398. $methods = array();
  399. foreach ($xml['stream:features'][0]['#']['mechanisms'][0]['#']['mechanism'] as $value)
  400. {
  401. $methods[] = $value['#'];
  402. }
  403. // we prefer DIGEST-MD5
  404. // we don't want to use plain authentication (neither does the server usually) if no encryption is in place
  405. // http://www.xmpp.org/extensions/attic/jep-0078-1.7.html
  406. // The plaintext mechanism SHOULD NOT be used unless the underlying stream is encrypted (using SSL or TLS)
  407. // and the client has verified that the server certificate is signed by a trusted certificate authority.
  408. if (in_array('DIGEST-MD5', $methods))
  409. {
  410. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
  411. }
  412. else if (in_array('PLAIN', $methods) && ($this->session['ssl'] || !empty($this->session['tls'])))
  413. {
  414. // http://www.ietf.org/rfc/rfc4616.txt (PLAIN SASL Mechanism)
  415. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='PLAIN'>"
  416. . base64_encode($this->username . '@' . $this->server . chr(0) . $this->username . chr(0) . $this->password) .
  417. '</auth>');
  418. }
  419. else if (in_array('ANONYMOUS', $methods))
  420. {
  421. $this->send("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='ANONYMOUS'/>");
  422. }
  423. else
  424. {
  425. // not good...
  426. $this->add_to_log('Error: No authentication method supported.');
  427. $this->disconnect();
  428. return false;
  429. }
  430. return $this->response($this->listen());
  431. }
  432. else
  433. {
  434. // ok, this is it. bye.
  435. $this->add_to_log('Error: Server does not offer SASL authentication.');
  436. $this->disconnect();
  437. return false;
  438. }
  439. break;
  440. case 'challenge':
  441. // continue with authentication...a challenge literally -_-
  442. $decoded = base64_decode($xml['challenge'][0]['#']);
  443. $decoded = $this->parse_data($decoded);
  444. if (!isset($decoded['digest-uri']))
  445. {
  446. $decoded['digest-uri'] = 'xmpp/'. $this->server;
  447. }
  448. // better generate a cnonce, maybe it's needed
  449. $decoded['cnonce'] = base64_encode(md5(uniqid(mt_rand(), true)));
  450. // second challenge?
  451. if (isset($decoded['rspauth']))
  452. {
  453. $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
  454. }
  455. else
  456. {
  457. // Make sure we only use 'auth' for qop (relevant for $this->encrypt_password())
  458. // If the <response> is choking up on the changed parameter we may need to adjust encrypt_password() directly
  459. if (isset($decoded['qop']) && $decoded['qop'] != 'auth' && strpos($decoded['qop'], 'auth') !== false)
  460. {
  461. $decoded['qop'] = 'auth';
  462. }
  463. $response = array(
  464. 'username' => $this->username,
  465. 'response' => $this->encrypt_password(array_merge($decoded, array('nc' => '00000001'))),
  466. 'charset' => 'utf-8',
  467. 'nc' => '00000001',
  468. 'qop' => 'auth', // only auth being supported
  469. );
  470. foreach (array('nonce', 'digest-uri', 'realm', 'cnonce') as $key)
  471. {
  472. if (isset($decoded[$key]))
  473. {
  474. $response[$key] = $decoded[$key];
  475. }
  476. }
  477. $this->send("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" . base64_encode($this->implode_data($response)) . '</response>');
  478. }
  479. return $this->response($this->listen());
  480. break;
  481. case 'failure':
  482. $this->add_to_log('Error: Server sent "failure".');
  483. $this->disconnect();
  484. return false;
  485. break;
  486. case 'proceed':
  487. // continue switching to TLS
  488. $meta = stream_get_meta_data($this->connection);
  489. socket_set_blocking($this->connection, 1);
  490. if (!stream_socket_enable_crypto($this->connection, true, STREAM_CRYPTO_METHOD_TLS_CLIENT))
  491. {
  492. $this->add_to_log('Error: TLS mode change failed.');
  493. return false;
  494. }
  495. socket_set_blocking($this->connection, $meta['blocked']);
  496. $this->session['tls'] = true;
  497. // new stream
  498. $this->send("<?xml version='1.0' encoding='UTF-8' ?" . ">\n");
  499. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  500. return $this->response($this->listen());
  501. break;
  502. case 'success':
  503. // Yay, authentication successful.
  504. $this->send("<stream:stream to='{$this->server}' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>\n");
  505. $this->session['authenticated'] = true;
  506. // we have to wait for another response
  507. return $this->response($this->listen());
  508. break;
  509. case 'iq':
  510. // we are not interested in IQs we did not expect
  511. if (!isset($xml['iq'][0]['@']['id']))
  512. {
  513. return false;
  514. }
  515. // multiple possibilities here
  516. switch ($xml['iq'][0]['@']['id'])
  517. {
  518. case 'bind_1':
  519. $this->session['jid'] = $xml['iq'][0]['#']['bind'][0]['#']['jid'][0]['#'];
  520. // and (maybe) yet another request to be able to send messages *finally*
  521. if ($this->session['sess_required'])
  522. {
  523. $this->send("<iq to='{$this->server}' type='set' id='sess_1'>
  524. <session xmlns='urn:ietf:params:xml:ns:xmpp-session'/>
  525. </iq>");
  526. return $this->response($this->listen());
  527. }
  528. return true;
  529. break;
  530. case 'sess_1':
  531. return true;
  532. break;
  533. case 'reg_1':
  534. $this->send("<iq type='set' id='reg_2'>
  535. <query xmlns='jabber:iq:register'>
  536. <username>" . utf8_htmlspecialchars($this->username) . "</username>
  537. <password>" . utf8_htmlspecialchars($this->password) . "</password>
  538. </query>
  539. </iq>");
  540. return $this->response($this->listen());
  541. break;
  542. case 'reg_2':
  543. // registration end
  544. if (isset($xml['iq'][0]['#']['error']))
  545. {
  546. $this->add_to_log('Warning: Registration failed.');
  547. return false;
  548. }
  549. return true;
  550. break;
  551. case 'unreg_1':
  552. return true;
  553. break;
  554. default:
  555. $this->add_to_log('Notice: Received unexpected IQ.');
  556. return false;
  557. break;
  558. }
  559. break;
  560. case 'message':
  561. // we are only interested in content...
  562. if (!isset($xml['message'][0]['#']['body']))
  563. {
  564. return false;
  565. }
  566. $message['body'] = $xml['message'][0]['#']['body'][0]['#'];
  567. $message['from'] = $xml['message'][0]['@']['from'];
  568. if (isset($xml['message'][0]['#']['subject']))
  569. {
  570. $message['subject'] = $xml['message'][0]['#']['subject'][0]['#'];
  571. }
  572. $this->session['messages'][] = $message;
  573. break;
  574. default:
  575. // hm...don't know this response
  576. $this->add_to_log('Notice: Unknown server response (' . key($xml) . ')');
  577. return false;
  578. break;
  579. }
  580. }
  581. function send_message($to, $text, $subject = '', $type = 'normal')
  582. {
  583. if (!isset($this->session['jid']))
  584. {
  585. return false;
  586. }
  587. if (!in_array($type, array('chat', 'normal', 'error', 'groupchat', 'headline')))
  588. {
  589. $type = 'normal';
  590. }
  591. return $this->send("<message from='" . utf8_htmlspecialchars($this->session['jid']) . "' to='" . utf8_htmlspecialchars($to) . "' type='$type' id='" . uniqid('msg') . "'>
  592. <subject>" . utf8_htmlspecialchars($subject) . "</subject>
  593. <body>" . utf8_htmlspecialchars($text) . "</body>
  594. </message>"
  595. );
  596. }
  597. /**
  598. * Encrypts a password as in RFC 2831
  599. * @param array $data Needs data from the client-server connection
  600. * @access public
  601. * @return string
  602. */
  603. function encrypt_password($data)
  604. {
  605. // let's me think about <challenge> again...
  606. foreach (array('realm', 'cnonce', 'digest-uri') as $key)
  607. {
  608. if (!isset($data[$key]))
  609. {
  610. $data[$key] = '';
  611. }
  612. }
  613. $pack = md5($this->username . ':' . $data['realm'] . ':' . $this->password);
  614. if (isset($data['authzid']))
  615. {
  616. $a1 = pack('H32', $pack) . sprintf(':%s:%s:%s', $data['nonce'], $data['cnonce'], $data['authzid']);
  617. }
  618. else
  619. {
  620. $a1 = pack('H32', $pack) . sprintf(':%s:%s', $data['nonce'], $data['cnonce']);
  621. }
  622. // should be: qop = auth
  623. $a2 = 'AUTHENTICATE:'. $data['digest-uri'];
  624. return md5(sprintf('%s:%s:%s:%s:%s:%s', md5($a1), $data['nonce'], $data['nc'], $data['cnonce'], $data['qop'], md5($a2)));
  625. }
  626. /**
  627. * parse_data like a="b",c="d",... or like a="a, b", c, d="e", f=g,...
  628. * @param string $data
  629. * @access public
  630. * @return array a => b ...
  631. */
  632. function parse_data($data)
  633. {
  634. $data = explode(',', $data);
  635. $pairs = array();
  636. $key = false;
  637. foreach ($data as $pair)
  638. {
  639. $dd = strpos($pair, '=');
  640. if ($dd)
  641. {
  642. $key = trim(substr($pair, 0, $dd));
  643. $pairs[$key] = trim(trim(substr($pair, $dd + 1)), '"');
  644. }
  645. else if (strpos(strrev(trim($pair)), '"') === 0 && $key)
  646. {
  647. // We are actually having something left from "a, b" values, add it to the last one we handled.
  648. $pairs[$key] .= ',' . trim(trim($pair), '"');
  649. continue;
  650. }
  651. }
  652. return $pairs;
  653. }
  654. /**
  655. * opposite of jabber::parse_data()
  656. * @param array $data
  657. * @access public
  658. * @return string
  659. */
  660. function implode_data($data)
  661. {
  662. $return = array();
  663. foreach ($data as $key => $value)
  664. {
  665. $return[] = $key . '="' . $value . '"';
  666. }
  667. return implode(',', $return);
  668. }
  669. /**
  670. * xmlize()
  671. * @author Hans Anderson
  672. * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
  673. */
  674. function xmlize($data, $skip_white = 1, $encoding = 'UTF-8')
  675. {
  676. $data = trim($data);
  677. if (substr($data, 0, 5) != '<?xml')
  678. {
  679. // mod
  680. $data = '<root>'. $data . '</root>';
  681. }
  682. $vals = $index = $array = array();
  683. $parser = xml_parser_create($encoding);
  684. xml_parser_set_option($parser, XML_OPTION_CASE_FOLDING, 0);
  685. xml_parser_set_option($parser, XML_OPTION_SKIP_WHITE, $skip_white);
  686. xml_parse_into_struct($parser, $data, $vals, $index);
  687. xml_parser_free($parser);
  688. $i = 0;
  689. $tagname = $vals[$i]['tag'];
  690. $array[$tagname][0]['@'] = (isset($vals[$i]['attributes'])) ? $vals[$i]['attributes'] : array();
  691. $array[$tagname][0]['#'] = $this->_xml_depth($vals, $i);
  692. if (substr($data, 0, 5) != '<?xml')
  693. {
  694. $array = $array['root'][0]['#'];
  695. }
  696. return $array;
  697. }
  698. /**
  699. * _xml_depth()
  700. * @author Hans Anderson
  701. * @copyright Hans Anderson / http://www.hansanderson.com/php/xml/
  702. */
  703. function _xml_depth($vals, &$i)
  704. {
  705. $children = array();
  706. if (isset($vals[$i]['value']))
  707. {
  708. array_push($children, $vals[$i]['value']);
  709. }
  710. while (++$i < sizeof($vals))
  711. {
  712. switch ($vals[$i]['type'])
  713. {
  714. case 'open':
  715. $tagname = (isset($vals[$i]['tag'])) ? $vals[$i]['tag'] : '';
  716. $size = (isset($children[$tagname])) ? sizeof($children[$tagname]) : 0;
  717. if (isset($vals[$i]['attributes']))
  718. {
  719. $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
  720. }
  721. $children[$tagname][$size]['#'] = $this->_xml_depth($vals, $i);
  722. break;
  723. case 'cdata':
  724. array_push($children, $vals[$i]['value']);
  725. break;
  726. case 'complete':
  727. $tagname = $vals[$i]['tag'];
  728. $size = (isset($children[$tagname])) ? sizeof($children[$tagname]) : 0;
  729. $children[$tagname][$size]['#'] = (isset($vals[$i]['value'])) ? $vals[$i]['value'] : array();
  730. if (isset($vals[$i]['attributes']))
  731. {
  732. $children[$tagname][$size]['@'] = $vals[$i]['attributes'];
  733. }
  734. break;
  735. case 'close':
  736. return $children;
  737. break;
  738. }
  739. }
  740. return $children;
  741. }
  742. }
  743. ?>