PageRenderTime 61ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/app/libs/PEAR/PEAR/Common.php

https://github.com/aborruso/geocloud2
PHP | 2040 lines | 1635 code | 70 blank | 335 comment | 114 complexity | 0878a16b3dfbbf8f7d193472b9ffd6df MD5 | raw file
Possible License(s): LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 4 |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2003 The PHP Group |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license, |
  9. // | that is bundled with this package in the file LICENSE, and is |
  10. // | available through the world-wide-web at the following url: |
  11. // | http://www.php.net/license/3_0.txt. |
  12. // | If you did not receive a copy of the PHP license and are unable to |
  13. // | obtain it through the world-wide-web, please send a note to |
  14. // | license@php.net so we can mail you a copy immediately. |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net> |
  17. // | Tomas V.V.Cox <cox@idecnet.com> |
  18. // +----------------------------------------------------------------------+
  19. //
  20. // $Id: Common.php,v 1.1 2009/03/26 18:56:32 mhoegh Exp $
  21. require_once 'PEAR.php';
  22. require_once 'Archive/Tar.php';
  23. require_once 'System.php';
  24. require_once 'PEAR/Config.php';
  25. // {{{ constants and globals
  26. /**
  27. * PEAR_Common error when an invalid PHP file is passed to PEAR_Common::analyzeSourceCode()
  28. */
  29. define('PEAR_COMMON_ERROR_INVALIDPHP', 1);
  30. define('_PEAR_COMMON_PACKAGE_NAME_PREG', '[A-Za-z][a-zA-Z0-9_]+');
  31. define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '$/');
  32. // this should allow: 1, 1.0, 1.0RC1, 1.0dev, 1.0dev123234234234, 1.0a1, 1.0b1, 1.0pl1
  33. define('_PEAR_COMMON_PACKAGE_VERSION_PREG', '\d+(?:\.\d+)*(?:[a-z]+\d*)?');
  34. define('PEAR_COMMON_PACKAGE_VERSION_PREG', '/^' . _PEAR_COMMON_PACKAGE_VERSION_PREG . '$/i');
  35. // XXX far from perfect :-)
  36. define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^(' . _PEAR_COMMON_PACKAGE_NAME_PREG . ')(-([.0-9a-zA-Z]+))?$/');
  37. /**
  38. * List of temporary files and directories registered by
  39. * PEAR_Common::addTempFile().
  40. * @var array
  41. */
  42. $GLOBALS['_PEAR_Common_tempfiles'] = array();
  43. /**
  44. * Valid maintainer roles
  45. * @var array
  46. */
  47. $GLOBALS['_PEAR_Common_maintainer_roles'] = array('lead','developer','contributor','helper');
  48. /**
  49. * Valid release states
  50. * @var array
  51. */
  52. $GLOBALS['_PEAR_Common_release_states'] = array('alpha','beta','stable','snapshot','devel');
  53. /**
  54. * Valid dependency types
  55. * @var array
  56. */
  57. $GLOBALS['_PEAR_Common_dependency_types'] = array('pkg','ext','php','prog','ldlib','rtlib','os','websrv','sapi');
  58. /**
  59. * Valid dependency relations
  60. * @var array
  61. */
  62. $GLOBALS['_PEAR_Common_dependency_relations'] = array('has','eq','lt','le','gt','ge','not');
  63. /**
  64. * Valid file roles
  65. * @var array
  66. */
  67. $GLOBALS['_PEAR_Common_file_roles'] = array('php','ext','test','doc','data','src','script');
  68. /**
  69. * Valid replacement types
  70. * @var array
  71. */
  72. $GLOBALS['_PEAR_Common_replacement_types'] = array('php-const', 'pear-config', 'package-info');
  73. /**
  74. * Valid "provide" types
  75. * @var array
  76. */
  77. $GLOBALS['_PEAR_Common_provide_types'] = array('ext', 'prog', 'class', 'function', 'feature', 'api');
  78. /**
  79. * Valid "provide" types
  80. * @var array
  81. */
  82. $GLOBALS['_PEAR_Common_script_phases'] = array('pre-install', 'post-install', 'pre-uninstall', 'post-uninstall', 'pre-build', 'post-build', 'pre-configure', 'post-configure', 'pre-setup', 'post-setup');
  83. // }}}
  84. /**
  85. * Class providing common functionality for PEAR adminsitration classes.
  86. */
  87. class PEAR_Common extends PEAR
  88. {
  89. // {{{ properties
  90. /** stack of elements, gives some sort of XML context */
  91. var $element_stack = array();
  92. /** name of currently parsed XML element */
  93. var $current_element;
  94. /** array of attributes of the currently parsed XML element */
  95. var $current_attributes = array();
  96. /** assoc with information about a package */
  97. var $pkginfo = array();
  98. /**
  99. * User Interface object (PEAR_Frontend_* class). If null,
  100. * the log() method uses print.
  101. * @var object
  102. */
  103. var $ui = null;
  104. /**
  105. * Configuration object (PEAR_Config).
  106. * @var object
  107. */
  108. var $config = null;
  109. var $current_path = null;
  110. /**
  111. * PEAR_SourceAnalyzer instance
  112. * @var object
  113. */
  114. var $source_analyzer = null;
  115. /**
  116. * Flag variable used to mark a valid package file
  117. * @var boolean
  118. * @access private
  119. */
  120. var $_validPackageFile;
  121. /**
  122. * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
  123. * @var array
  124. * @access private
  125. */
  126. var $_packageSortTree;
  127. // }}}
  128. // {{{ constructor
  129. /**
  130. * PEAR_Common constructor
  131. *
  132. * @access public
  133. */
  134. function PEAR_Common()
  135. {
  136. parent::PEAR();
  137. $this->config = &PEAR_Config::singleton();
  138. $this->debug = $this->config->get('verbose');
  139. }
  140. // }}}
  141. // {{{ destructor
  142. /**
  143. * PEAR_Common destructor
  144. *
  145. * @access private
  146. */
  147. function _PEAR_Common()
  148. {
  149. // doesn't work due to bug #14744
  150. //$tempfiles = $this->_tempfiles;
  151. $tempfiles =& $GLOBALS['_PEAR_Common_tempfiles'];
  152. while ($file = array_shift($tempfiles)) {
  153. if (@is_dir($file)) {
  154. System::rm(array('-rf', $file));
  155. } elseif (file_exists($file)) {
  156. unlink($file);
  157. }
  158. }
  159. }
  160. // }}}
  161. // {{{ addTempFile()
  162. /**
  163. * Register a temporary file or directory. When the destructor is
  164. * executed, all registered temporary files and directories are
  165. * removed.
  166. *
  167. * @param string $file name of file or directory
  168. *
  169. * @return void
  170. *
  171. * @access public
  172. */
  173. function addTempFile($file)
  174. {
  175. $GLOBALS['_PEAR_Common_tempfiles'][] = $file;
  176. }
  177. // }}}
  178. // {{{ mkDirHier()
  179. /**
  180. * Wrapper to System::mkDir(), creates a directory as well as
  181. * any necessary parent directories.
  182. *
  183. * @param string $dir directory name
  184. *
  185. * @return bool TRUE on success, or a PEAR error
  186. *
  187. * @access public
  188. */
  189. function mkDirHier($dir)
  190. {
  191. $this->log(2, "+ create dir $dir");
  192. return System::mkDir(array('-p', $dir));
  193. }
  194. // }}}
  195. // {{{ log()
  196. /**
  197. * Logging method.
  198. *
  199. * @param int $level log level (0 is quiet, higher is noisier)
  200. * @param string $msg message to write to the log
  201. *
  202. * @return void
  203. *
  204. * @access public
  205. */
  206. function log($level, $msg, $append_crlf = true)
  207. {
  208. if ($this->debug >= $level) {
  209. if (is_object($this->ui)) {
  210. $this->ui->log($msg, $append_crlf);
  211. } else {
  212. print "$msg\n";
  213. }
  214. }
  215. }
  216. // }}}
  217. // {{{ mkTempDir()
  218. /**
  219. * Create and register a temporary directory.
  220. *
  221. * @param string $tmpdir (optional) Directory to use as tmpdir.
  222. * Will use system defaults (for example
  223. * /tmp or c:\windows\temp) if not specified
  224. *
  225. * @return string name of created directory
  226. *
  227. * @access public
  228. */
  229. function mkTempDir($tmpdir = '')
  230. {
  231. if ($tmpdir) {
  232. $topt = array('-t', $tmpdir);
  233. } else {
  234. $topt = array();
  235. }
  236. $topt = array_merge($topt, array('-d', 'pear'));
  237. if (!$tmpdir = System::mktemp($topt)) {
  238. return false;
  239. }
  240. $this->addTempFile($tmpdir);
  241. return $tmpdir;
  242. }
  243. // }}}
  244. // {{{ setFrontendObject()
  245. /**
  246. * Set object that represents the frontend to be used.
  247. *
  248. * @param object Reference of the frontend object
  249. * @return void
  250. * @access public
  251. */
  252. function setFrontendObject(&$ui)
  253. {
  254. $this->ui = &$ui;
  255. }
  256. // }}}
  257. // {{{ _unIndent()
  258. /**
  259. * Unindent given string (?)
  260. *
  261. * @param string $str The string that has to be unindented.
  262. * @return string
  263. * @access private
  264. */
  265. function _unIndent($str)
  266. {
  267. // remove leading newlines
  268. $str = preg_replace('/^[\r\n]+/', '', $str);
  269. // find whitespace at the beginning of the first line
  270. $indent_len = strspn($str, " \t");
  271. $indent = substr($str, 0, $indent_len);
  272. $data = '';
  273. // remove the same amount of whitespace from following lines
  274. foreach (explode("\n", $str) as $line) {
  275. if (substr($line, 0, $indent_len) == $indent) {
  276. $data .= substr($line, $indent_len) . "\n";
  277. }
  278. }
  279. return $data;
  280. }
  281. // }}}
  282. // {{{ _element_start()
  283. /**
  284. * XML parser callback for starting elements. Used while package
  285. * format version is not yet known.
  286. *
  287. * @param resource $xp XML parser resource
  288. * @param string $name name of starting element
  289. * @param array $attribs element attributes, name => value
  290. *
  291. * @return void
  292. *
  293. * @access private
  294. */
  295. function _element_start($xp, $name, $attribs)
  296. {
  297. array_push($this->element_stack, $name);
  298. $this->current_element = $name;
  299. $spos = sizeof($this->element_stack) - 2;
  300. $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
  301. $this->current_attributes = $attribs;
  302. switch ($name) {
  303. case 'package': {
  304. $this->_validPackageFile = true;
  305. if (isset($attribs['version'])) {
  306. $vs = preg_replace('/[^0-9a-z]/', '_', $attribs['version']);
  307. } else {
  308. $vs = '1_0';
  309. }
  310. $elem_start = '_element_start_'. $vs;
  311. $elem_end = '_element_end_'. $vs;
  312. $cdata = '_pkginfo_cdata_'. $vs;
  313. if (!method_exists($this, $elem_start) ||
  314. !method_exists($this, $elem_end) ||
  315. !method_exists($this, $cdata)) {
  316. $this->raiseError("No handlers for package.xml version $attribs[version]");
  317. return;
  318. }
  319. xml_set_element_handler($xp, $elem_start, $elem_end);
  320. xml_set_character_data_handler($xp, $cdata);
  321. break;
  322. }
  323. }
  324. }
  325. // }}}
  326. // {{{ _element_end()
  327. /**
  328. * XML parser callback for ending elements. Used while package
  329. * format version is not yet known.
  330. *
  331. * @param resource $xp XML parser resource
  332. * @param string $name name of ending element
  333. *
  334. * @return void
  335. *
  336. * @access private
  337. */
  338. function _element_end($xp, $name)
  339. {
  340. }
  341. // }}}
  342. // Support for package DTD v1.0:
  343. // {{{ _element_start_1_0()
  344. /**
  345. * XML parser callback for ending elements. Used for version 1.0
  346. * packages.
  347. *
  348. * @param resource $xp XML parser resource
  349. * @param string $name name of ending element
  350. *
  351. * @return void
  352. *
  353. * @access private
  354. */
  355. function _element_start_1_0($xp, $name, $attribs)
  356. {
  357. array_push($this->element_stack, $name);
  358. $this->current_element = $name;
  359. $spos = sizeof($this->element_stack) - 2;
  360. $this->prev_element = ($spos >= 0) ? $this->element_stack[$spos] : '';
  361. $this->current_attributes = $attribs;
  362. $this->cdata = '';
  363. switch ($name) {
  364. case 'dir':
  365. if ($this->in_changelog) {
  366. break;
  367. }
  368. if ($attribs['name'] != '/') {
  369. $this->dir_names[] = $attribs['name'];
  370. }
  371. if (isset($attribs['baseinstalldir'])) {
  372. $this->dir_install = $attribs['baseinstalldir'];
  373. }
  374. if (isset($attribs['role'])) {
  375. $this->dir_role = $attribs['role'];
  376. }
  377. break;
  378. case 'file':
  379. if ($this->in_changelog) {
  380. break;
  381. }
  382. if (isset($attribs['name'])) {
  383. $path = '';
  384. if (count($this->dir_names)) {
  385. foreach ($this->dir_names as $dir) {
  386. $path .= $dir . DIRECTORY_SEPARATOR;
  387. }
  388. }
  389. $path .= $attribs['name'];
  390. unset($attribs['name']);
  391. $this->current_path = $path;
  392. $this->filelist[$path] = $attribs;
  393. // Set the baseinstalldir only if the file don't have this attrib
  394. if (!isset($this->filelist[$path]['baseinstalldir']) &&
  395. isset($this->dir_install))
  396. {
  397. $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
  398. }
  399. // Set the Role
  400. if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
  401. $this->filelist[$path]['role'] = $this->dir_role;
  402. }
  403. }
  404. break;
  405. case 'replace':
  406. if (!$this->in_changelog) {
  407. $this->filelist[$this->current_path]['replacements'][] = $attribs;
  408. }
  409. break;
  410. case 'maintainers':
  411. $this->pkginfo['maintainers'] = array();
  412. $this->m_i = 0; // maintainers array index
  413. break;
  414. case 'maintainer':
  415. // compatibility check
  416. if (!isset($this->pkginfo['maintainers'])) {
  417. $this->pkginfo['maintainers'] = array();
  418. $this->m_i = 0;
  419. }
  420. $this->pkginfo['maintainers'][$this->m_i] = array();
  421. $this->current_maintainer =& $this->pkginfo['maintainers'][$this->m_i];
  422. break;
  423. case 'changelog':
  424. $this->pkginfo['changelog'] = array();
  425. $this->c_i = 0; // changelog array index
  426. $this->in_changelog = true;
  427. break;
  428. case 'release':
  429. if ($this->in_changelog) {
  430. $this->pkginfo['changelog'][$this->c_i] = array();
  431. $this->current_release = &$this->pkginfo['changelog'][$this->c_i];
  432. } else {
  433. $this->current_release = &$this->pkginfo;
  434. }
  435. break;
  436. case 'deps':
  437. if (!$this->in_changelog) {
  438. $this->pkginfo['release_deps'] = array();
  439. }
  440. break;
  441. case 'dep':
  442. // dependencies array index
  443. if (!$this->in_changelog) {
  444. $this->d_i++;
  445. $this->pkginfo['release_deps'][$this->d_i] = $attribs;
  446. }
  447. break;
  448. case 'configureoptions':
  449. if (!$this->in_changelog) {
  450. $this->pkginfo['configure_options'] = array();
  451. }
  452. break;
  453. case 'configureoption':
  454. if (!$this->in_changelog) {
  455. $this->pkginfo['configure_options'][] = $attribs;
  456. }
  457. break;
  458. case 'provides':
  459. if (empty($attribs['type']) || empty($attribs['name'])) {
  460. break;
  461. }
  462. $attribs['explicit'] = true;
  463. $this->pkginfo['provides']["$attribs[type];$attribs[name]"] = $attribs;
  464. break;
  465. }
  466. }
  467. // }}}
  468. // {{{ _element_end_1_0()
  469. /**
  470. * XML parser callback for ending elements. Used for version 1.0
  471. * packages.
  472. *
  473. * @param resource $xp XML parser resource
  474. * @param string $name name of ending element
  475. *
  476. * @return void
  477. *
  478. * @access private
  479. */
  480. function _element_end_1_0($xp, $name)
  481. {
  482. $data = trim($this->cdata);
  483. switch ($name) {
  484. case 'name':
  485. switch ($this->prev_element) {
  486. case 'package':
  487. // XXX should we check the package name here?
  488. $this->pkginfo['package'] = ereg_replace('[^a-zA-Z0-9._]', '_', $data);
  489. break;
  490. case 'maintainer':
  491. $this->current_maintainer['name'] = $data;
  492. break;
  493. }
  494. break;
  495. case 'summary':
  496. $this->pkginfo['summary'] = $data;
  497. break;
  498. case 'description':
  499. $data = $this->_unIndent($this->cdata);
  500. $this->pkginfo['description'] = $data;
  501. break;
  502. case 'user':
  503. $this->current_maintainer['handle'] = $data;
  504. break;
  505. case 'email':
  506. $this->current_maintainer['email'] = $data;
  507. break;
  508. case 'role':
  509. $this->current_maintainer['role'] = $data;
  510. break;
  511. case 'version':
  512. $data = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $data);
  513. if ($this->in_changelog) {
  514. $this->current_release['version'] = $data;
  515. } else {
  516. $this->pkginfo['version'] = $data;
  517. }
  518. break;
  519. case 'date':
  520. if ($this->in_changelog) {
  521. $this->current_release['release_date'] = $data;
  522. } else {
  523. $this->pkginfo['release_date'] = $data;
  524. }
  525. break;
  526. case 'notes':
  527. // try to "de-indent" release notes in case someone
  528. // has been over-indenting their xml ;-)
  529. $data = $this->_unIndent($this->cdata);
  530. if ($this->in_changelog) {
  531. $this->current_release['release_notes'] = $data;
  532. } else {
  533. $this->pkginfo['release_notes'] = $data;
  534. }
  535. break;
  536. case 'warnings':
  537. if ($this->in_changelog) {
  538. $this->current_release['release_warnings'] = $data;
  539. } else {
  540. $this->pkginfo['release_warnings'] = $data;
  541. }
  542. break;
  543. case 'state':
  544. if ($this->in_changelog) {
  545. $this->current_release['release_state'] = $data;
  546. } else {
  547. $this->pkginfo['release_state'] = $data;
  548. }
  549. break;
  550. case 'license':
  551. if ($this->in_changelog) {
  552. $this->current_release['release_license'] = $data;
  553. } else {
  554. $this->pkginfo['release_license'] = $data;
  555. }
  556. break;
  557. case 'dep':
  558. if ($data && !$this->in_changelog) {
  559. $this->pkginfo['release_deps'][$this->d_i]['name'] = $data;
  560. }
  561. break;
  562. case 'dir':
  563. if ($this->in_changelog) {
  564. break;
  565. }
  566. array_pop($this->dir_names);
  567. break;
  568. case 'file':
  569. if ($this->in_changelog) {
  570. break;
  571. }
  572. if ($data) {
  573. $path = '';
  574. if (count($this->dir_names)) {
  575. foreach ($this->dir_names as $dir) {
  576. $path .= $dir . DIRECTORY_SEPARATOR;
  577. }
  578. }
  579. $path .= $data;
  580. $this->filelist[$path] = $this->current_attributes;
  581. // Set the baseinstalldir only if the file don't have this attrib
  582. if (!isset($this->filelist[$path]['baseinstalldir']) &&
  583. isset($this->dir_install))
  584. {
  585. $this->filelist[$path]['baseinstalldir'] = $this->dir_install;
  586. }
  587. // Set the Role
  588. if (!isset($this->filelist[$path]['role']) && isset($this->dir_role)) {
  589. $this->filelist[$path]['role'] = $this->dir_role;
  590. }
  591. }
  592. break;
  593. case 'maintainer':
  594. if (empty($this->pkginfo['maintainers'][$this->m_i]['role'])) {
  595. $this->pkginfo['maintainers'][$this->m_i]['role'] = 'lead';
  596. }
  597. $this->m_i++;
  598. break;
  599. case 'release':
  600. if ($this->in_changelog) {
  601. $this->c_i++;
  602. }
  603. break;
  604. case 'changelog':
  605. $this->in_changelog = false;
  606. break;
  607. }
  608. array_pop($this->element_stack);
  609. $spos = sizeof($this->element_stack) - 1;
  610. $this->current_element = ($spos > 0) ? $this->element_stack[$spos] : '';
  611. $this->cdata = '';
  612. }
  613. // }}}
  614. // {{{ _pkginfo_cdata_1_0()
  615. /**
  616. * XML parser callback for character data. Used for version 1.0
  617. * packages.
  618. *
  619. * @param resource $xp XML parser resource
  620. * @param string $name character data
  621. *
  622. * @return void
  623. *
  624. * @access private
  625. */
  626. function _pkginfo_cdata_1_0($xp, $data)
  627. {
  628. if (isset($this->cdata)) {
  629. $this->cdata .= $data;
  630. }
  631. }
  632. // }}}
  633. // {{{ infoFromTgzFile()
  634. /**
  635. * Returns information about a package file. Expects the name of
  636. * a gzipped tar file as input.
  637. *
  638. * @param string $file name of .tgz file
  639. *
  640. * @return array array with package information
  641. *
  642. * @access public
  643. *
  644. */
  645. function infoFromTgzFile($file)
  646. {
  647. if (!@is_file($file)) {
  648. return $this->raiseError("could not open file \"$file\"");
  649. }
  650. $tar = new Archive_Tar($file);
  651. if ($this->debug <= 1) {
  652. $tar->pushErrorHandling(PEAR_ERROR_RETURN);
  653. }
  654. $content = $tar->listContent();
  655. if ($this->debug <= 1) {
  656. $tar->popErrorHandling();
  657. }
  658. if (!is_array($content)) {
  659. $file = realpath($file);
  660. return $this->raiseError("Could not get contents of package \"$file\"".
  661. '. Invalid tgz file.');
  662. }
  663. $xml = null;
  664. foreach ($content as $file) {
  665. $name = $file['filename'];
  666. if ($name == 'package.xml') {
  667. $xml = $name;
  668. break;
  669. } elseif (ereg('package.xml$', $name, $match)) {
  670. $xml = $match[0];
  671. break;
  672. }
  673. }
  674. $tmpdir = System::mkTemp(array('-d', 'pear'));
  675. $this->addTempFile($tmpdir);
  676. if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
  677. return $this->raiseError('could not extract the package.xml file');
  678. }
  679. return $this->infoFromDescriptionFile("$tmpdir/$xml");
  680. }
  681. // }}}
  682. // {{{ infoFromDescriptionFile()
  683. /**
  684. * Returns information about a package file. Expects the name of
  685. * a package xml file as input.
  686. *
  687. * @param string $descfile name of package xml file
  688. *
  689. * @return array array with package information
  690. *
  691. * @access public
  692. *
  693. */
  694. function infoFromDescriptionFile($descfile)
  695. {
  696. if (!@is_file($descfile) || !is_readable($descfile) ||
  697. (!$fp = @fopen($descfile, 'r'))) {
  698. return $this->raiseError("Unable to open $descfile");
  699. }
  700. // read the whole thing so we only get one cdata callback
  701. // for each block of cdata
  702. $data = fread($fp, filesize($descfile));
  703. return $this->infoFromString($data);
  704. }
  705. // }}}
  706. // {{{ infoFromString()
  707. /**
  708. * Returns information about a package file. Expects the contents
  709. * of a package xml file as input.
  710. *
  711. * @param string $data name of package xml file
  712. *
  713. * @return array array with package information
  714. *
  715. * @access public
  716. *
  717. */
  718. function infoFromString($data)
  719. {
  720. require_once('PEAR/Dependency.php');
  721. if (PEAR_Dependency::checkExtension($error, 'xml')) {
  722. return $this->raiseError($error);
  723. }
  724. $xp = @xml_parser_create();
  725. if (!$xp) {
  726. return $this->raiseError('Unable to create XML parser');
  727. }
  728. xml_set_object($xp, $this);
  729. xml_set_element_handler($xp, '_element_start', '_element_end');
  730. xml_set_character_data_handler($xp, '_pkginfo_cdata');
  731. xml_parser_set_option($xp, XML_OPTION_CASE_FOLDING, false);
  732. $this->element_stack = array();
  733. $this->pkginfo = array('provides' => array());
  734. $this->current_element = false;
  735. unset($this->dir_install);
  736. $this->pkginfo['filelist'] = array();
  737. $this->filelist =& $this->pkginfo['filelist'];
  738. $this->dir_names = array();
  739. $this->in_changelog = false;
  740. $this->d_i = 0;
  741. $this->cdata = '';
  742. $this->_validPackageFile = false;
  743. if (!xml_parse($xp, $data, 1)) {
  744. $code = xml_get_error_code($xp);
  745. $msg = sprintf("XML error: %s at line %d",
  746. xml_error_string($code),
  747. xml_get_current_line_number($xp));
  748. xml_parser_free($xp);
  749. return $this->raiseError($msg, $code);
  750. }
  751. xml_parser_free($xp);
  752. if (!$this->_validPackageFile) {
  753. return $this->raiseError('Invalid Package File, no <package> tag');
  754. }
  755. foreach ($this->pkginfo as $k => $v) {
  756. if (!is_array($v)) {
  757. $this->pkginfo[$k] = trim($v);
  758. }
  759. }
  760. return $this->pkginfo;
  761. }
  762. // }}}
  763. // {{{ infoFromAny()
  764. /**
  765. * Returns package information from different sources
  766. *
  767. * This method is able to extract information about a package
  768. * from a .tgz archive or from a XML package definition file.
  769. *
  770. * @access public
  771. * @param string Filename of the source ('package.xml', '<package>.tgz')
  772. * @return string
  773. */
  774. function infoFromAny($info)
  775. {
  776. if (is_string($info) && file_exists($info)) {
  777. $tmp = substr($info, -4);
  778. if ($tmp == '.xml') {
  779. $info = $this->infoFromDescriptionFile($info);
  780. } elseif ($tmp == '.tar' || $tmp == '.tgz') {
  781. $info = $this->infoFromTgzFile($info);
  782. } else {
  783. $fp = fopen($info, "r");
  784. $test = fread($fp, 5);
  785. fclose($fp);
  786. if ($test == "<?xml") {
  787. $info = $this->infoFromDescriptionFile($info);
  788. } else {
  789. $info = $this->infoFromTgzFile($info);
  790. }
  791. }
  792. if (PEAR::isError($info)) {
  793. return $this->raiseError($info);
  794. }
  795. }
  796. return $info;
  797. }
  798. // }}}
  799. // {{{ xmlFromInfo()
  800. /**
  801. * Return an XML document based on the package info (as returned
  802. * by the PEAR_Common::infoFrom* methods).
  803. *
  804. * @param array $pkginfo package info
  805. *
  806. * @return string XML data
  807. *
  808. * @access public
  809. */
  810. function xmlFromInfo($pkginfo)
  811. {
  812. static $maint_map = array(
  813. "handle" => "user",
  814. "name" => "name",
  815. "email" => "email",
  816. "role" => "role",
  817. );
  818. $ret = "<?xml version=\"1.0\" encoding=\"ISO-8859-1\" ?>\n";
  819. $ret .= "<!DOCTYPE package SYSTEM \"http://pear.php.net/dtd/package-1.0\">\n";
  820. $ret .= "<package version=\"1.0\">
  821. <name>$pkginfo[package]</name>
  822. <summary>".htmlspecialchars($pkginfo['summary'])."</summary>
  823. <description>".htmlspecialchars($pkginfo['description'])."</description>
  824. <maintainers>
  825. ";
  826. foreach ($pkginfo['maintainers'] as $maint) {
  827. $ret .= " <maintainer>\n";
  828. foreach ($maint_map as $idx => $elm) {
  829. $ret .= " <$elm>";
  830. $ret .= htmlspecialchars($maint[$idx]);
  831. $ret .= "</$elm>\n";
  832. }
  833. $ret .= " </maintainer>\n";
  834. }
  835. $ret .= " </maintainers>\n";
  836. $ret .= $this->_makeReleaseXml($pkginfo);
  837. if (@sizeof($pkginfo['changelog']) > 0) {
  838. $ret .= " <changelog>\n";
  839. foreach ($pkginfo['changelog'] as $oldrelease) {
  840. $ret .= $this->_makeReleaseXml($oldrelease, true);
  841. }
  842. $ret .= " </changelog>\n";
  843. }
  844. $ret .= "</package>\n";
  845. return $ret;
  846. }
  847. // }}}
  848. // {{{ _makeReleaseXml()
  849. /**
  850. * Generate part of an XML description with release information.
  851. *
  852. * @param array $pkginfo array with release information
  853. * @param bool $changelog whether the result will be in a changelog element
  854. *
  855. * @return string XML data
  856. *
  857. * @access private
  858. */
  859. function _makeReleaseXml($pkginfo, $changelog = false)
  860. {
  861. // XXX QUOTE ENTITIES IN PCDATA, OR EMBED IN CDATA BLOCKS!!
  862. $indent = $changelog ? " " : "";
  863. $ret = "$indent <release>\n";
  864. if (!empty($pkginfo['version'])) {
  865. $ret .= "$indent <version>$pkginfo[version]</version>\n";
  866. }
  867. if (!empty($pkginfo['release_date'])) {
  868. $ret .= "$indent <date>$pkginfo[release_date]</date>\n";
  869. }
  870. if (!empty($pkginfo['release_license'])) {
  871. $ret .= "$indent <license>$pkginfo[release_license]</license>\n";
  872. }
  873. if (!empty($pkginfo['release_state'])) {
  874. $ret .= "$indent <state>$pkginfo[release_state]</state>\n";
  875. }
  876. if (!empty($pkginfo['release_notes'])) {
  877. $ret .= "$indent <notes>".htmlspecialchars($pkginfo['release_notes'])."</notes>\n";
  878. }
  879. if (!empty($pkginfo['release_warnings'])) {
  880. $ret .= "$indent <warnings>".htmlspecialchars($pkginfo['release_warnings'])."</warnings>\n";
  881. }
  882. if (isset($pkginfo['release_deps']) && sizeof($pkginfo['release_deps']) > 0) {
  883. $ret .= "$indent <deps>\n";
  884. foreach ($pkginfo['release_deps'] as $dep) {
  885. $ret .= "$indent <dep type=\"$dep[type]\" rel=\"$dep[rel]\"";
  886. if (isset($dep['version'])) {
  887. $ret .= " version=\"$dep[version]\"";
  888. }
  889. if (isset($dep['optional'])) {
  890. $ret .= " optional=\"$dep[optional]\"";
  891. }
  892. if (isset($dep['name'])) {
  893. $ret .= ">$dep[name]</dep>\n";
  894. } else {
  895. $ret .= "/>\n";
  896. }
  897. }
  898. $ret .= "$indent </deps>\n";
  899. }
  900. if (isset($pkginfo['configure_options'])) {
  901. $ret .= "$indent <configureoptions>\n";
  902. foreach ($pkginfo['configure_options'] as $c) {
  903. $ret .= "$indent <configureoption name=\"".
  904. htmlspecialchars($c['name']) . "\"";
  905. if (isset($c['default'])) {
  906. $ret .= " default=\"" . htmlspecialchars($c['default']) . "\"";
  907. }
  908. $ret .= " prompt=\"" . htmlspecialchars($c['prompt']) . "\"";
  909. $ret .= "/>\n";
  910. }
  911. $ret .= "$indent </configureoptions>\n";
  912. }
  913. if (isset($pkginfo['provides'])) {
  914. foreach ($pkginfo['provides'] as $key => $what) {
  915. $ret .= "$indent <provides type=\"$what[type]\" ";
  916. $ret .= "name=\"$what[name]\" ";
  917. if (isset($what['extends'])) {
  918. $ret .= "extends=\"$what[extends]\" ";
  919. }
  920. $ret .= "/>\n";
  921. }
  922. }
  923. if (isset($pkginfo['filelist'])) {
  924. $ret .= "$indent <filelist>\n";
  925. foreach ($pkginfo['filelist'] as $file => $fa) {
  926. @$ret .= "$indent <file role=\"$fa[role]\"";
  927. if (isset($fa['baseinstalldir'])) {
  928. $ret .= ' baseinstalldir="' .
  929. htmlspecialchars($fa['baseinstalldir']) . '"';
  930. }
  931. if (isset($fa['md5sum'])) {
  932. $ret .= " md5sum=\"$fa[md5sum]\"";
  933. }
  934. if (isset($fa['platform'])) {
  935. $ret .= " platform=\"$fa[platform]\"";
  936. }
  937. if (!empty($fa['install-as'])) {
  938. $ret .= ' install-as="' .
  939. htmlspecialchars($fa['install-as']) . '"';
  940. }
  941. $ret .= ' name="' . htmlspecialchars($file) . '"';
  942. if (empty($fa['replacements'])) {
  943. $ret .= "/>\n";
  944. } else {
  945. $ret .= ">\n";
  946. foreach ($fa['replacements'] as $r) {
  947. $ret .= "$indent <replace";
  948. foreach ($r as $k => $v) {
  949. $ret .= " $k=\"" . htmlspecialchars($v) .'"';
  950. }
  951. $ret .= "/>\n";
  952. }
  953. @$ret .= "$indent </file>\n";
  954. }
  955. }
  956. $ret .= "$indent </filelist>\n";
  957. }
  958. $ret .= "$indent </release>\n";
  959. return $ret;
  960. }
  961. // }}}
  962. // {{{ validatePackageInfo()
  963. /**
  964. * Validate XML package definition file.
  965. *
  966. * @param string $info Filename of the package archive or of the
  967. * package definition file
  968. * @param array $errors Array that will contain the errors
  969. * @param array $warnings Array that will contain the warnings
  970. * @param string $dir_prefix (optional) directory where source files
  971. * may be found, or empty if they are not available
  972. * @access public
  973. * @return boolean
  974. */
  975. function validatePackageInfo($info, &$errors, &$warnings, $dir_prefix = '')
  976. {
  977. if (PEAR::isError($info = $this->infoFromAny($info))) {
  978. return $this->raiseError($info);
  979. }
  980. if (!is_array($info)) {
  981. return false;
  982. }
  983. $errors = array();
  984. $warnings = array();
  985. if (!isset($info['package'])) {
  986. $errors[] = 'missing package name';
  987. } elseif (!$this->validPackageName($info['package'])) {
  988. $errors[] = 'invalid package name';
  989. }
  990. $this->_packageName = $pn = $info['package'];
  991. if (empty($info['summary'])) {
  992. $errors[] = 'missing summary';
  993. } elseif (strpos(trim($info['summary']), "\n") !== false) {
  994. $warnings[] = 'summary should be on a single line';
  995. }
  996. if (empty($info['description'])) {
  997. $errors[] = 'missing description';
  998. }
  999. if (empty($info['release_license'])) {
  1000. $errors[] = 'missing license';
  1001. }
  1002. if (!isset($info['version'])) {
  1003. $errors[] = 'missing version';
  1004. } elseif (!$this->validPackageVersion($info['version'])) {
  1005. $errors[] = 'invalid package release version';
  1006. }
  1007. if (empty($info['release_state'])) {
  1008. $errors[] = 'missing release state';
  1009. } elseif (!in_array($info['release_state'], PEAR_Common::getReleaseStates())) {
  1010. $errors[] = "invalid release state `$info[release_state]', should be one of: "
  1011. . implode(' ', PEAR_Common::getReleaseStates());
  1012. }
  1013. if (empty($info['release_date'])) {
  1014. $errors[] = 'missing release date';
  1015. } elseif (!preg_match('/^\d{4}-\d\d-\d\d$/', $info['release_date'])) {
  1016. $errors[] = "invalid release date `$info[release_date]', format is YYYY-MM-DD";
  1017. }
  1018. if (empty($info['release_notes'])) {
  1019. $errors[] = "missing release notes";
  1020. }
  1021. if (empty($info['maintainers'])) {
  1022. $errors[] = 'no maintainer(s)';
  1023. } else {
  1024. $i = 1;
  1025. foreach ($info['maintainers'] as $m) {
  1026. if (empty($m['handle'])) {
  1027. $errors[] = "maintainer $i: missing handle";
  1028. }
  1029. if (empty($m['role'])) {
  1030. $errors[] = "maintainer $i: missing role";
  1031. } elseif (!in_array($m['role'], PEAR_Common::getUserRoles())) {
  1032. $errors[] = "maintainer $i: invalid role `$m[role]', should be one of: "
  1033. . implode(' ', PEAR_Common::getUserRoles());
  1034. }
  1035. if (empty($m['name'])) {
  1036. $errors[] = "maintainer $i: missing name";
  1037. }
  1038. if (empty($m['email'])) {
  1039. $errors[] = "maintainer $i: missing email";
  1040. }
  1041. $i++;
  1042. }
  1043. }
  1044. if (!empty($info['release_deps'])) {
  1045. $i = 1;
  1046. foreach ($info['release_deps'] as $d) {
  1047. if (empty($d['type'])) {
  1048. $errors[] = "dependency $i: missing type";
  1049. } elseif (!in_array($d['type'], PEAR_Common::getDependencyTypes())) {
  1050. $errors[] = "dependency $i: invalid type '$d[type]', should be one of: " .
  1051. implode(' ', PEAR_Common::getDependencyTypes());
  1052. }
  1053. if (empty($d['rel'])) {
  1054. $errors[] = "dependency $i: missing relation";
  1055. } elseif (!in_array($d['rel'], PEAR_Common::getDependencyRelations())) {
  1056. $errors[] = "dependency $i: invalid relation '$d[rel]', should be one of: "
  1057. . implode(' ', PEAR_Common::getDependencyRelations());
  1058. }
  1059. if (!empty($d['optional'])) {
  1060. if (!in_array($d['optional'], array('yes', 'no'))) {
  1061. $errors[] = "dependency $i: invalid relation optional attribute '$d[optional]', should be one of: yes no";
  1062. }
  1063. }
  1064. if ($d['rel'] != 'has' && empty($d['version'])) {
  1065. $warnings[] = "dependency $i: missing version";
  1066. } elseif ($d['rel'] == 'has' && !empty($d['version'])) {
  1067. $warnings[] = "dependency $i: version ignored for `has' dependencies";
  1068. }
  1069. if ($d['type'] == 'php' && !empty($d['name'])) {
  1070. $warnings[] = "dependency $i: name ignored for php type dependencies";
  1071. } elseif ($d['type'] != 'php' && empty($d['name'])) {
  1072. $errors[] = "dependency $i: missing name";
  1073. }
  1074. $i++;
  1075. }
  1076. }
  1077. if (!empty($info['configure_options'])) {
  1078. $i = 1;
  1079. foreach ($info['configure_options'] as $c) {
  1080. if (empty($c['name'])) {
  1081. $errors[] = "configure option $i: missing name";
  1082. }
  1083. if (empty($c['prompt'])) {
  1084. $errors[] = "configure option $i: missing prompt";
  1085. }
  1086. $i++;
  1087. }
  1088. }
  1089. if (empty($info['filelist'])) {
  1090. $errors[] = 'no files';
  1091. } else {
  1092. foreach ($info['filelist'] as $file => $fa) {
  1093. if (empty($fa['role'])) {
  1094. $errors[] = "file $file: missing role";
  1095. continue;
  1096. } elseif (!in_array($fa['role'], PEAR_Common::getFileRoles())) {
  1097. $errors[] = "file $file: invalid role, should be one of: "
  1098. . implode(' ', PEAR_Common::getFileRoles());
  1099. }
  1100. if ($fa['role'] == 'php' && $dir_prefix) {
  1101. $this->log(1, "Analyzing $file");
  1102. $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
  1103. if ($srcinfo) {
  1104. $this->buildProvidesArray($srcinfo);
  1105. }
  1106. }
  1107. // (ssb) Any checks we can do for baseinstalldir?
  1108. // (cox) Perhaps checks that either the target dir and
  1109. // baseInstall doesn't cointain "../../"
  1110. }
  1111. }
  1112. $this->_packageName = $pn = $info['package'];
  1113. $pnl = strlen($pn);
  1114. foreach ((array)$this->pkginfo['provides'] as $key => $what) {
  1115. if (isset($what['explicit'])) {
  1116. // skip conformance checks if the provides entry is
  1117. // specified in the package.xml file
  1118. continue;
  1119. }
  1120. extract($what);
  1121. if ($type == 'class') {
  1122. if (!strncasecmp($name, $pn, $pnl)) {
  1123. continue;
  1124. }
  1125. $warnings[] = "in $file: class \"$name\" not prefixed with package name \"$pn\"";
  1126. } elseif ($type == 'function') {
  1127. if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
  1128. continue;
  1129. }
  1130. $warnings[] = "in $file: function \"$name\" not prefixed with package name \"$pn\"";
  1131. }
  1132. }
  1133. return true;
  1134. }
  1135. // }}}
  1136. // {{{ buildProvidesArray()
  1137. /**
  1138. * Build a "provides" array from data returned by
  1139. * analyzeSourceCode(). The format of the built array is like
  1140. * this:
  1141. *
  1142. * array(
  1143. * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
  1144. * ...
  1145. * )
  1146. *
  1147. *
  1148. * @param array $srcinfo array with information about a source file
  1149. * as returned by the analyzeSourceCode() method.
  1150. *
  1151. * @return void
  1152. *
  1153. * @access public
  1154. *
  1155. */
  1156. function buildProvidesArray($srcinfo)
  1157. {
  1158. $file = basename($srcinfo['source_file']);
  1159. $pn = '';
  1160. if (isset($this->_packageName)) {
  1161. $pn = $this->_packageName;
  1162. }
  1163. $pnl = strlen($pn);
  1164. foreach ($srcinfo['declared_classes'] as $class) {
  1165. $key = "class;$class";
  1166. if (isset($this->pkginfo['provides'][$key])) {
  1167. continue;
  1168. }
  1169. $this->pkginfo['provides'][$key] =
  1170. array('file'=> $file, 'type' => 'class', 'name' => $class);
  1171. if (isset($srcinfo['inheritance'][$class])) {
  1172. $this->pkginfo['provides'][$key]['extends'] =
  1173. $srcinfo['inheritance'][$class];
  1174. }
  1175. }
  1176. foreach ($srcinfo['declared_methods'] as $class => $methods) {
  1177. foreach ($methods as $method) {
  1178. $function = "$class::$method";
  1179. $key = "function;$function";
  1180. if ($method{0} == '_' || !strcasecmp($method, $class) ||
  1181. isset($this->pkginfo['provides'][$key])) {
  1182. continue;
  1183. }
  1184. $this->pkginfo['provides'][$key] =
  1185. array('file'=> $file, 'type' => 'function', 'name' => $function);
  1186. }
  1187. }
  1188. foreach ($srcinfo['declared_functions'] as $function) {
  1189. $key = "function;$function";
  1190. if ($function{0} == '_' || isset($this->pkginfo['provides'][$key])) {
  1191. continue;
  1192. }
  1193. if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
  1194. $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
  1195. }
  1196. $this->pkginfo['provides'][$key] =
  1197. array('file'=> $file, 'type' => 'function', 'name' => $function);
  1198. }
  1199. }
  1200. // }}}
  1201. // {{{ analyzeSourceCode()
  1202. /**
  1203. * Analyze the source code of the given PHP file
  1204. *
  1205. * @param string Filename of the PHP file
  1206. * @return mixed
  1207. * @access public
  1208. */
  1209. function analyzeSourceCode($file)
  1210. {
  1211. if (!function_exists("token_get_all")) {
  1212. return false;
  1213. }
  1214. if (!$fp = @fopen($file, "r")) {
  1215. return false;
  1216. }
  1217. $contents = fread($fp, filesize($file));
  1218. $tokens = token_get_all($contents);
  1219. /*
  1220. for ($i = 0; $i < sizeof($tokens); $i++) {
  1221. @list($token, $data) = $tokens[$i];
  1222. if (is_string($token)) {
  1223. var_dump($token);
  1224. } else {
  1225. print token_name($token) . ' ';
  1226. var_dump(rtrim($data));
  1227. }
  1228. }
  1229. */
  1230. $look_for = 0;
  1231. $paren_level = 0;
  1232. $bracket_level = 0;
  1233. $brace_level = 0;
  1234. $lastphpdoc = '';
  1235. $current_class = '';
  1236. $current_class_level = -1;
  1237. $current_function = '';
  1238. $current_function_level = -1;
  1239. $declared_classes = array();
  1240. $declared_functions = array();
  1241. $declared_methods = array();
  1242. $used_classes = array();
  1243. $used_functions = array();
  1244. $extends = array();
  1245. $nodeps = array();
  1246. $inquote = false;
  1247. for ($i = 0; $i < sizeof($tokens); $i++) {
  1248. if (is_array($tokens[$i])) {
  1249. list($token, $data) = $tokens[$i];
  1250. } else {
  1251. $token = $tokens[$i];
  1252. $data = '';
  1253. }
  1254. if ($inquote) {
  1255. if ($token != '"') {
  1256. continue;
  1257. } else {
  1258. $inquote = false;
  1259. }
  1260. }
  1261. switch ($token) {
  1262. case '"':
  1263. $inquote = true;
  1264. break;
  1265. case T_CURLY_OPEN:
  1266. case T_DOLLAR_OPEN_CURLY_BRACES:
  1267. case '{': $brace_level++; continue 2;
  1268. case '}':
  1269. $brace_level--;
  1270. if ($current_class_level == $brace_level) {
  1271. $current_class = '';
  1272. $current_class_level = -1;
  1273. }
  1274. if ($current_function_level == $brace_level) {
  1275. $current_function = '';
  1276. $current_function_level = -1;
  1277. }
  1278. continue 2;
  1279. case '[': $bracket_level++; continue 2;
  1280. case ']': $bracket_level--; continue 2;
  1281. case '(': $paren_level++; continue 2;
  1282. case ')': $paren_level--; continue 2;
  1283. case T_CLASS:
  1284. if (($current_class_level != -1) || ($current_function_level != -1)) {
  1285. PEAR::raiseError("Parser error: Invalid PHP file $file",
  1286. PEAR_COMMON_ERROR_INVALIDPHP);
  1287. return false;
  1288. }
  1289. case T_FUNCTION:
  1290. case T_NEW:
  1291. case T_EXTENDS:
  1292. $look_for = $token;
  1293. continue 2;
  1294. case T_STRING:
  1295. if ($look_for == T_CLASS) {
  1296. $current_class = $data;
  1297. $current_class_level = $brace_level;
  1298. $declared_classes[] = $current_class;
  1299. } elseif ($look_for == T_EXTENDS) {
  1300. $extends[$current_class] = $data;
  1301. } elseif ($look_for == T_FUNCTION) {
  1302. if ($current_class) {
  1303. $current_function = "$current_class::$data";
  1304. $declared_methods[$current_class][] = $data;
  1305. } else {
  1306. $current_function = $data;
  1307. $declared_functions[] = $current_function;
  1308. }
  1309. $current_function_level = $brace_level;
  1310. $m = array();
  1311. } elseif ($look_for == T_NEW) {
  1312. $used_classes[$data] = true;
  1313. }
  1314. $look_for = 0;
  1315. continue 2;
  1316. case T_VARIABLE:
  1317. $look_for = 0;
  1318. continue 2;
  1319. case T_COMMENT:
  1320. if (preg_match('!^/\*\*\s!', $data)) {
  1321. $lastphpdoc = $data;
  1322. if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
  1323. $nodeps = array_merge($nodeps, $m[1]);
  1324. }
  1325. }
  1326. continue 2;
  1327. case T_DOUBLE_COLON:
  1328. if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
  1329. PEAR::raiseError("Parser error: Invalid PHP file $file",
  1330. PEAR_COMMON_ERROR_INVALIDPHP);
  1331. return false;
  1332. }
  1333. $class = $tokens[$i - 1][1];
  1334. if (strtolower($class) != 'parent') {
  1335. $used_classes[$class] = true;
  1336. }
  1337. continue 2;
  1338. }
  1339. }
  1340. return array(
  1341. "source_file" => $file,
  1342. "declared_classes" => $declared_classes,
  1343. "declared_methods" => $declared_methods,
  1344. "declared_functions" => $declared_functions,
  1345. "used_classes" => array_diff(array_keys($used_classes), $nodeps),
  1346. "inheritance" => $extends,
  1347. );
  1348. }
  1349. // }}}
  1350. // {{{ betterStates()
  1351. /**
  1352. * Return an array containing all of the states that are more s…

Large files files are truncated, but you can click here to view the full file