PageRenderTime 58ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/bak/PEAR/PEAR.old/Common.php

https://bitbucket.org/kucing2k/ediassoc
PHP | 2094 lines | 1692 code | 70 blank | 332 comment | 114 complexity | d62e4853fb436e25163ad6d7df616c9e MD5 | raw file
Possible License(s): BSD-3-Clause, LGPL-2.1, BSD-2-Clause, GPL-2.0

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

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