PageRenderTime 53ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/PEAR/PEAR/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
  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)) {
  1317. PEAR::raiseError("Parser error: Invalid PHP file $file",
  1318. PEAR_COMMON_ERROR_INVALIDPHP);
  1319. return false;
  1320. }
  1321. case T_FUNCTION:
  1322. case T_NEW:
  1323. case T_EXTENDS:
  1324. case T_IMPLEMENTS:
  1325. $look_for = $token;
  1326. continue 2;
  1327. case T_STRING:
  1328. if (version_compare(zend_version(), '2.0', '<')) {
  1329. if (in_array(strtolower($data),
  1330. array('public', 'private', 'protected', 'abstract',
  1331. 'interface', 'implements', 'clone', 'throw')
  1332. )) {
  1333. PEAR::raiseError('Error: PHP5 packages must be packaged by php 5 PEAR');
  1334. return false;
  1335. }
  1336. }
  1337. if ($look_for == T_CLASS) {
  1338. $current_class = $data;
  1339. $current_class_level = $brace_level;
  1340. $declared_classes[] = $current_class;
  1341. } elseif ($look_for == T_INTERFACE) {
  1342. $current_interface = $data;
  1343. $current_class_level = $brace_level;
  1344. $declared_interfaces[] = $current_interface;
  1345. } elseif ($look_for == T_IMPLEMENTS) {
  1346. $implements[$current_class] = $data;
  1347. } elseif ($look_for == T_EXTENDS) {
  1348. $extends[$current_class] = $data;
  1349. } elseif ($look_for == T_FUNCTION) {
  1350. if ($current_class) {
  1351. $current_function = "$current_class::$data";
  1352. $declared_methods[$current_class][] = $data;
  1353. } elseif ($current_interface) {
  1354. $current_function = "$current_interface::$data";
  1355. $declared_methods[$current_interface][] = $data;
  1356. } else {
  1357. $current_function = $data;
  1358. $declared_functions[] = $current_function;
  1359. }
  1360. $current_function_level = $brace_level;
  1361. $m = array();
  1362. } elseif ($look_for == T_NEW) {
  1363. $used_classes[$data] = true;
  1364. }
  1365. $look_for = 0;
  1366. continue 2;
  1367. case T_VARIABLE:
  1368. $look_for = 0;
  1369. continue 2;
  1370. case T_DOC_COMMENT:
  1371. case T_COMMENT:
  1372. if (preg_match('!^/\*\*\s!', $data)) {
  1373. $lastphpdoc = $data;
  1374. if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
  1375. $nodeps = array_merge($nodeps, $m[1]);
  1376. }
  1377. }
  1378. continue 2;
  1379. case T_DOUBLE_COLON:
  1380. if (!($tokens[$i - 1][0] == T_WHITESPACE || $tokens[$i - 1][0] == T_STRING)) {
  1381. PEAR::raiseError("Parser error: Invalid PHP file $file",
  1382. PEAR_COMMON_ERROR_INVALIDPHP);
  1383. return false;
  1384. }
  1385. $class = $tokens[$i - 1][1];
  1386. if (strtolower($class) != 'parent') {
  1387. $used_classes[$class] = true;
  1388. }
  1389. continue 2;
  1390. }
  1391. }
  1392. return array(
  1393. "source_file" => $file,
  1394. "declared_classes" => $declared_classes,
  1395. "declared_interfaces" => $declared_interfaces,
  1396. "declared_methods" => $declared_methods,
  1397. "declared_functions" => $declared_functions,
  1398. "used_classes" => array_diff(array_keys($used_classes), $nodeps),
  1399. "inheritance" => $extends,
  1400. "implements" => $implements,
  1401. );
  1402. }
  1403. // }}}
  1404. // {{{ betterStates()
  1405. /**
  1406. * Return an array containing all of the states that are more stable than
  1407. * or equal to the passed in state
  1408. *
  1409. * @param string Release state
  1410. * @param boolean Determines whether to include $state in the list
  1411. * @return false|array False if $state is not a valid release state
  1412. */
  1413. function betterStates($state, $include = false)
  1414. {
  1415. static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
  1416. $i = array_search($state, $states);
  1417. if ($i === false) {
  1418. return false;
  1419. }
  1420. if ($include) {
  1421. $i--;
  1422. }
  1423. return array_slice($states, $i + 1);
  1424. }
  1425. // }}}
  1426. // {{{ detectDependencies()
  1427. function detectDependencies($any, $status_callback = null)
  1428. {
  1429. if (!function_exists("token_get_all")) {
  1430. return false;
  1431. }
  1432. if (PEAR::isError($info = $this->infoFromAny($any))) {
  1433. return $this->raiseError($info);
  1434. }
  1435. if (!is_array($info)) {
  1436. return false;
  1437. }
  1438. $deps = array();
  1439. $used_c = $decl_c = $decl_f = $decl_m = array();
  1440. foreach ($info['filelist'] as $file => $fa) {
  1441. $tmp = $this->analyzeSourceCode($file);
  1442. $used_c = @array_merge($used_c, $tmp['used_classes']);
  1443. $decl_c = @array_merge($decl_c, $tmp['declared_classes']);
  1444. $decl_f = @array_merge($decl_f, $tmp['declared_functions']);
  1445. $decl_m = @array_merge($decl_m, $tmp['declared_methods']);
  1446. $inheri = @array_merge($inheri, $tmp['inheritance']);
  1447. }
  1448. $used_c = array_unique($used_c);
  1449. $decl_c = array_unique($decl_c);
  1450. $undecl_c = array_diff($used_c, $decl_c);
  1451. return array('used_classes' => $used_c,
  1452. 'declared_classes' => $decl_c,
  1453. 'declared_methods' => $decl_m,
  1454. 'declared_functions' => $decl_f,
  1455. 'undeclared_classes' => $undecl_c,
  1456. 'inheritance' => $inheri,
  1457. );
  1458. }
  1459. // }}}
  1460. // {{{ getUserRoles()
  1461. /**
  1462. * Get the valid roles for a PEAR package maintainer
  1463. *
  1464. * @return array
  1465. * @static
  1466. */
  1467. function getUserRoles()
  1468. {
  1469. return $GLOBALS['_PEAR_Common_maintainer_roles'];
  1470. }
  1471. // }}}
  1472. // {{{ getReleaseStates()
  1473. /**
  1474. * Get the valid package release states of packages
  1475. *
  1476. * @return array
  1477. * @static
  1478. */
  1479. function getReleaseStates()
  1480. {
  1481. return $GLOBALS['_PEAR_Common_release_states'];
  1482. }
  1483. // }}}
  1484. // {{{ getDependencyTypes()
  1485. /**
  1486. * Get the implemented dependency types (php, ext, pkg etc.)
  1487. *
  1488. * @return array
  1489. * @static
  1490. */
  1491. function getDependencyTypes()
  1492. {
  1493. return $GLOBALS['_PEAR_Common_dependency_types'];
  1494. }
  1495. // }}}
  1496. // {{{ getDependencyRelations()
  1497. /**
  1498. * Get the implemented dependency relations (has, lt, ge etc.)
  1499. *
  1500. * @return array
  1501. * @static
  1502. */
  1503. function getDependencyRelations()
  1504. {
  1505. return $GLOBALS['_PEAR_Common_dependency_relations'];
  1506. }
  1507. // }}}
  1508. // {{{ getFileRoles()
  1509. /**
  1510. * Get the implemented file roles
  1511. *
  1512. * @return array
  1513. * @static
  1514. */
  1515. function getFileRoles()
  1516. {
  1517. return $GLOBALS['_PEAR_Common_file_roles'];
  1518. }
  1519. // }}}
  1520. // {{{ getReplacementTypes()
  1521. /**
  1522. * Get the implemented file replacement types in
  1523. *
  1524. * @return array
  1525. * @static
  1526. */
  1527. function getReplacementTypes()
  1528. {
  1529. return $GLOBALS['_PEAR_Common_replacement_types'];
  1530. }
  1531. // }}}
  1532. // {{{ getProvideTypes()
  1533. /**
  1534. * Get the implemented file replacement types in
  1535. *
  1536. * @return array
  1537. * @static
  1538. */
  1539. function getProvideTypes()
  1540. {
  1541. return $GLOBALS['_PEAR_Common_provide_types'];
  1542. }
  1543. // }}}
  1544. // {{{ getScriptPhases()
  1545. /**
  1546. * Get the implemented file replacement types in
  1547. *
  1548. * @return array
  1549. * @static
  1550. */
  1551. function getScriptPhases()
  1552. {
  1553. return $GLOBALS['_PEAR_Common_script_phases'];
  1554. }
  1555. // }}}
  1556. // {{{ validPackageName()
  1557. /**
  1558. * Test whether a string contains a valid package name.
  1559. *
  1560. * @param string $name the package name to test
  1561. *
  1562. * @return bool
  1563. *
  1564. * @access public
  1565. */
  1566. function validPackageName($name)
  1567. {
  1568. return (bool)preg_match(PEAR_COMMON_PACKAGE_NAME_PREG, $name);
  1569. }
  1570. // }}}
  1571. // {{{ validPackageVersion()
  1572. /**
  1573. * Test whether a string contains a valid package version.
  1574. *
  1575. * @param string $ver the package version to test
  1576. *
  1577. * @return bool
  1578. *
  1579. * @access public
  1580. */
  1581. function validPackageVersion($ver)
  1582. {
  1583. return (bool)preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver);
  1584. }
  1585. // }}}
  1586. // {{{ downloadHttp()
  1587. /**
  1588. * Download a file through HTTP. Considers suggested file name in
  1589. * Content-disposition: header and can run a callback function for
  1590. * different events. The callback will be called with two
  1591. * parameters: the callback type, and parameters. The implemented
  1592. * callback types are:
  1593. *
  1594. * 'setup' called at the very beginning, parameter is a UI object
  1595. * that should be used for all output
  1596. * 'message' the parameter is a string with an informational message
  1597. * 'saveas' may be used to save with a different file name, the
  1598. * parameter is the filename that is about to be used.
  1599. * If a 'saveas' callback returns a non-empty string,
  1600. * that file name will be used as the filename instead.
  1601. * Note that $save_dir will not be affected by this, only
  1602. * the basename of the file.
  1603. * 'start' download is starting, parameter is number of bytes
  1604. * that are expected, or -1 if unknown
  1605. * 'bytesread' parameter is the number of bytes read so far
  1606. * 'done' download is complete, parameter is the total number
  1607. * of bytes read
  1608. * 'connfailed' if the TCP connection fails, this callback is called
  1609. * with array(host,port,errno,errmsg)
  1610. * 'writefailed' if writing to disk fails, this callback is called
  1611. * with array(destfile,errmsg)
  1612. *
  1613. * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1614. * setting), the proxy will be used.
  1615. *
  1616. * @param string $url the URL to download
  1617. * @param object $ui PEAR_Frontend_* instance
  1618. * @param object $config PEAR_Config instance
  1619. * @param string $save_dir (optional) directory to save file in
  1620. * @param mixed $callback (optional) function/method to call for status
  1621. * updates
  1622. *
  1623. * @return string Returns the full path of the downloaded file or a PEAR
  1624. * error on failure. If the error is caused by
  1625. * socket-related errors, the error object will
  1626. * have the fsockopen error code available through
  1627. * getCode().
  1628. *
  1629. * @access public
  1630. */
  1631. function downloadHttp($url, &$ui, $save_dir = '.', $callback = null)
  1632. {
  1633. if ($callback) {
  1634. call_user_func($callback, 'setup', array(&$ui));
  1635. }
  1636. if (preg_match('!^http://([^/:?#]*)(:(\d+))?(/.*)!', $url, $matches)) {
  1637. list(,$host,,$port,$path) = $matches;
  1638. }
  1639. if (isset($this)) {
  1640. $config = &$this->config;
  1641. } else {
  1642. $config = &PEAR_Config::singleton();
  1643. }
  1644. $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1645. if ($proxy = parse_url($config->get('http_proxy'))) {
  1646. $proxy_host = @$proxy['host'];
  1647. $proxy_port = @$proxy['port'];
  1648. $proxy_user = @$proxy['user'];
  1649. $proxy_pass = @$proxy['pass'];
  1650. if ($proxy_port == '') {
  1651. $proxy_port = 8080;
  1652. }
  1653. if ($callback) {
  1654. call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1655. }
  1656. }
  1657. if (empty($port)) {
  1658. $port = 80;
  1659. }
  1660. if ($proxy_host != '') {
  1661. $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1662. if (!$fp) {
  1663. if ($callback) {
  1664. call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1665. $errno, $errstr));
  1666. }
  1667. return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1668. }
  1669. $request = "GET $url HTTP/1.0\r\n";
  1670. } else {
  1671. $fp = @fsockopen($host, $port, $errno, $errstr);
  1672. if (!$fp) {
  1673. if ($callback) {
  1674. call_user_func($callback, 'connfailed', array($host, $port,
  1675. $errno, $errstr));
  1676. }
  1677. return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1678. }
  1679. $request = "GET $path HTTP/1.0\r\n";
  1680. }
  1681. $request .= "Host: $host:$port\r\n".
  1682. "User-Agent: PHP/".PHP_VERSION."\r\n";
  1683. if ($proxy_host != '' && $proxy_user != '') {
  1684. $request .= 'Proxy-Authorization: Basic ' .
  1685. base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1686. }
  1687. $request .= "\r\n";
  1688. fwrite($fp, $request);
  1689. $headers = array();
  1690. while (trim($line = fgets($fp, 1024))) {
  1691. if (preg_match('/^([^:]+):\s+(.*)\s*$/', $line, $matches)) {
  1692. $headers[strtolower($matches[1])] = trim($matches[2]);
  1693. } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1694. if ($matches[1] != 200) {
  1695. return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
  1696. }
  1697. }
  1698. }
  1699. if (isset($headers['content-disposition']) &&
  1700. preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|$)/', $headers['content-disposition'], $matches)) {
  1701. $save_as = basename($matches[1]);
  1702. } else {
  1703. $save_as = basename($url);
  1704. }
  1705. if ($callback) {
  1706. $tmp = call_user_func($callback, 'saveas', $save_as);
  1707. if ($tmp) {
  1708. $save_as = $tmp;
  1709. }
  1710. }
  1711. $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1712. if (!$wp = @fopen($dest_file, 'wb')) {
  1713. fclose($fp);
  1714. if ($callback) {
  1715. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1716. }
  1717. return PEAR::raiseError("could not open $dest_file for writing");
  1718. }
  1719. if (isset($headers['content-length'])) {
  1720. $length = $headers['content-length'];
  1721. } else {
  1722. $length = -1;
  1723. }
  1724. $bytes = 0;
  1725. if ($callback) {
  1726. call_user_func($callback, 'start', array(basename($dest_file), $length));
  1727. }
  1728. while ($data = @fread($fp, 1024)) {
  1729. $bytes += strlen($data);
  1730. if ($callback) {
  1731. call_user_func($callback, 'bytesread', $bytes);
  1732. }
  1733. if (!@fwrite($wp, $data)) {
  1734. fclose($fp);
  1735. if ($callback) {
  1736. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1737. }
  1738. return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1739. }
  1740. }
  1741. fclose($fp);
  1742. fclose($wp);
  1743. if ($callback) {
  1744. call_user_func($callback, 'done', $bytes);
  1745. }
  1746. return $dest_file;
  1747. }
  1748. // }}}
  1749. // {{{ sortPkgDeps()
  1750. /**
  1751. * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1752. *
  1753. * It also removes duplicate dependencies
  1754. * @param array
  1755. * @param boolean Sort packages in reverse order if true
  1756. * @return array array of array(packagefilename, package.xml contents)
  1757. */
  1758. function sortPkgDeps(&$packages, $uninstall = false)
  1759. {
  1760. $ret = array();
  1761. if ($uninstall) {
  1762. foreach($packages as $packageinfo) {
  1763. $ret[] = array('info' => $packageinfo);
  1764. }
  1765. } else {
  1766. foreach($packages as $packagefile) {
  1767. if (!is_array($packagefile)) {
  1768. $ret[] = array('file' => $packagefile,
  1769. 'info' => $a = $this->infoFromAny($packagefile),
  1770. 'pkg' => $a['package']);
  1771. } else {
  1772. $ret[] = $packagefile;
  1773. }
  1774. }
  1775. }
  1776. $checkdupes = array();
  1777. $newret = array();
  1778. foreach($ret as $i => $p) {
  1779. if (!isset($checkdupes[$p['info']['package']])) {
  1780. $checkdupes[$p['info']['package']][] = $i;
  1781. $newret[] = $p;
  1782. }
  1783. }
  1784. $this->_packageSortTree = $this->_getPkgDepTree($newret);
  1785. $func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps';
  1786. usort($newret, array(&$this, $func));
  1787. $this->_packageSortTree = null;
  1788. $packages = $newret;
  1789. }
  1790. // }}}
  1791. // {{{ _sortPkgDeps()
  1792. /**
  1793. * Compare two package's package.xml, and sort
  1794. * so that dependencies are installed first
  1795. *
  1796. * This is a crude compare, real dependency checking is done on install.
  1797. * The only purpose this serves is to make the command-line
  1798. * order-independent (you can list a dependent package first, and
  1799. * installation occurs in the order required)
  1800. * @access private
  1801. */
  1802. function _sortPkgDeps($p1, $p2)
  1803. {
  1804. $p1name = $p1['info']['package'];
  1805. $p2name = $p2['info']['package'];
  1806. $p1deps = $this->_getPkgDeps($p1);
  1807. $p2deps = $this->_getPkgDeps($p2);
  1808. if (!count($p1deps) && !count($p2deps)) {
  1809. return 0; // order makes no difference
  1810. }
  1811. if (!count($p1deps)) {
  1812. return -1; // package 2 has dependencies, package 1 doesn't
  1813. }
  1814. if (!count($p2deps)) {
  1815. return 1; // package 1 has dependencies, package 2 doesn't
  1816. }
  1817. // both have dependencies
  1818. if (in_array($p1name, $p2deps)) {
  1819. return -1; // put package 1 first: package 2 depends on package 1
  1820. }
  1821. if (in_array($p2name, $p1deps)) {
  1822. return 1; // put package 2 first: package 1 depends on package 2
  1823. }
  1824. if ($this->_removedDependency($p1name, $p2name)) {
  1825. return -1; // put package 1 first: package 2 depends on packages that depend on package 1
  1826. }
  1827. if ($this->_removedDependency($p2name, $p1name)) {
  1828. return 1; // put package 2 first: package 1 depends on packages that depend on package 2
  1829. }
  1830. // doesn't really matter if neither depends on the other
  1831. return 0;
  1832. }
  1833. // }}}
  1834. // {{{ _sortPkgDepsRev()
  1835. /**
  1836. * Compare two package's package.xml, and sort
  1837. * so that dependencies are uninstalled last
  1838. *
  1839. * This is a crude compare, real dependency checking is done on uninstall.
  1840. * The only purpose this serves is to make the command-line
  1841. * order-independent (you can list a dependency first, and
  1842. * uninstallation occurs in the order required)
  1843. * @access private
  1844. */
  1845. function _sortPkgDepsRev($p1, $p2)
  1846. {
  1847. $p1name = $p1['info']['package'];
  1848. $p2name = $p2['info']['package'];
  1849. $p1deps = $this->_getRevPkgDeps($p1);
  1850. $p2deps = $this->_getRevPkgDeps($p2);
  1851. if (!count($p1deps) && !count($p2deps)) {
  1852. return 0; // order makes no difference
  1853. }
  1854. if (!count($p1deps)) {
  1855. return 1; // package 2 has dependencies, package 1 doesn't
  1856. }
  1857. if (!count($p2deps)) {
  1858. return -1; // package 2 has dependencies, package 1 doesn't
  1859. }
  1860. // both have dependencies
  1861. if (in_array($p1name, $p2deps)) {
  1862. return 1; // put package 1 last
  1863. }
  1864. if (in_array($p2name, $p1deps)) {
  1865. return -1; // put package 2 last
  1866. }
  1867. if ($this->_removedDependency($p1name, $p2name)) {
  1868. return 1; // put package 1 last: package 2 depends on packages that depend on package 1
  1869. }
  1870. if ($this->_removedDependency($p2name, $p1name)) {
  1871. return -1; // put package 2 last: package 1 depends on packages that depend on package 2
  1872. }
  1873. // doesn't really matter if neither depends on the other
  1874. return 0;
  1875. }
  1876. // }}}
  1877. // {{{ _getPkgDeps()
  1878. /**
  1879. * get an array of package dependency names
  1880. * @param array
  1881. * @return array
  1882. * @access private
  1883. */
  1884. function _getPkgDeps($p)
  1885. {
  1886. if (!isset($p['info']['releases'])) {
  1887. return $this->_getRevPkgDeps($p);
  1888. }
  1889. $rel = array_shift($p['info']['releases']);
  1890. if (!isset($rel['deps'])) {
  1891. return array();
  1892. }
  1893. $ret = array();
  1894. foreach($rel['deps'] as $dep) {
  1895. if ($dep['type'] == 'pkg') {
  1896. $ret[] = $dep['name'];
  1897. }
  1898. }
  1899. return $ret;
  1900. }
  1901. // }}}
  1902. // {{{ _getPkgDeps()
  1903. /**
  1904. * get an array representation of the package dependency tree
  1905. * @return array
  1906. * @access private
  1907. */
  1908. function _getPkgDepTree($packages)
  1909. {
  1910. $tree = array();
  1911. foreach ($packages as $p) {
  1912. $package = $p['info']['package'];
  1913. $deps = $this->_getPkgDeps($p);
  1914. $tree[$package] = $deps;
  1915. }
  1916. return $tree;
  1917. }
  1918. // }}}
  1919. // {{{ _removedDependency($p1, $p2)
  1920. /**
  1921. * get an array of package dependency names for uninstall
  1922. * @param string package 1 name
  1923. * @param string package 2 name
  1924. * @return bool
  1925. * @access private
  1926. */
  1927. function _removedDependency($p1, $p2)
  1928. {
  1929. if (empty($this->_packageSortTree[$p2])) {
  1930. return false;
  1931. }
  1932. if (!in_array($p1, $this->_packageSortTree[$p2])) {
  1933. foreach ($this->_packageSortTree[$p2] as $potential) {
  1934. if ($this->_removedDependency($p1, $potential)) {
  1935. return true;
  1936. }
  1937. }
  1938. return false;
  1939. }
  1940. return true;
  1941. }
  1942. // }}}
  1943. // {{{ _getRevPkgDeps()
  1944. /**
  1945. * get an array of package dependency names for uninstall
  1946. * @param array
  1947. * @return array
  1948. * @access private
  1949. */
  1950. function _getRevPkgDeps($p)
  1951. {
  1952. if (!isset($p['info']['release_deps'])) {
  1953. return array();
  1954. }
  1955. $ret = array();
  1956. foreach($p['info']['release_deps'] as $dep) {
  1957. if ($dep['type'] == 'pkg') {
  1958. $ret[] = $dep['name'];
  1959. }
  1960. }
  1961. return $ret;
  1962. }
  1963. // }}}
  1964. }
  1965. ?>