/gespac/config/PEAR/PEAR/Downloader.php

http://gespac.googlecode.com/ · PHP · 1735 lines · 1294 code · 164 blank · 277 comment · 297 complexity · 0646fba5c60d4b906889973c8e7ac6ae MD5 · raw file

Large files are truncated click here to view the full file

  1. <?php
  2. /**
  3. * PEAR_Downloader, the PEAR Installer's download utility class
  4. *
  5. * PHP versions 4 and 5
  6. *
  7. * @category pear
  8. * @package PEAR
  9. * @author Greg Beaver <cellog@php.net>
  10. * @author Stig Bakken <ssb@php.net>
  11. * @author Tomas V. V. Cox <cox@idecnet.com>
  12. * @author Martin Jansen <mj@php.net>
  13. * @copyright 1997-2009 The Authors
  14. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  15. * @version CVS: $Id: Downloader.php 296767 2010-03-25 00:58:33Z dufuz $
  16. * @link http://pear.php.net/package/PEAR
  17. * @since File available since Release 1.3.0
  18. */
  19. /**
  20. * Needed for constants, extending
  21. */
  22. require_once 'PEAR/Common.php';
  23. define('PEAR_INSTALLER_OK', 1);
  24. define('PEAR_INSTALLER_FAILED', 0);
  25. define('PEAR_INSTALLER_SKIPPED', -1);
  26. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  27. /**
  28. * Administration class used to download anything from the internet (PEAR Packages,
  29. * static URLs, xml files)
  30. *
  31. * @category pear
  32. * @package PEAR
  33. * @author Greg Beaver <cellog@php.net>
  34. * @author Stig Bakken <ssb@php.net>
  35. * @author Tomas V. V. Cox <cox@idecnet.com>
  36. * @author Martin Jansen <mj@php.net>
  37. * @copyright 1997-2009 The Authors
  38. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  39. * @version Release: 1.9.1
  40. * @link http://pear.php.net/package/PEAR
  41. * @since Class available since Release 1.3.0
  42. */
  43. class PEAR_Downloader extends PEAR_Common
  44. {
  45. /**
  46. * @var PEAR_Registry
  47. * @access private
  48. */
  49. var $_registry;
  50. /**
  51. * Preferred Installation State (snapshot, devel, alpha, beta, stable)
  52. * @var string|null
  53. * @access private
  54. */
  55. var $_preferredState;
  56. /**
  57. * Options from command-line passed to Install.
  58. *
  59. * Recognized options:<br />
  60. * - onlyreqdeps : install all required dependencies as well
  61. * - alldeps : install all dependencies, including optional
  62. * - installroot : base relative path to install files in
  63. * - force : force a download even if warnings would prevent it
  64. * - nocompress : download uncompressed tarballs
  65. * @see PEAR_Command_Install
  66. * @access private
  67. * @var array
  68. */
  69. var $_options;
  70. /**
  71. * Downloaded Packages after a call to download().
  72. *
  73. * Format of each entry:
  74. *
  75. * <code>
  76. * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  77. * 'info' => array() // parsed package.xml
  78. * );
  79. * </code>
  80. * @access private
  81. * @var array
  82. */
  83. var $_downloadedPackages = array();
  84. /**
  85. * Packages slated for download.
  86. *
  87. * This is used to prevent downloading a package more than once should it be a dependency
  88. * for two packages to be installed.
  89. * Format of each entry:
  90. *
  91. * <pre>
  92. * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
  93. * );
  94. * </pre>
  95. * @access private
  96. * @var array
  97. */
  98. var $_toDownload = array();
  99. /**
  100. * Array of every package installed, with names lower-cased.
  101. *
  102. * Format:
  103. * <code>
  104. * array('package1' => 0, 'package2' => 1, );
  105. * </code>
  106. * @var array
  107. */
  108. var $_installed = array();
  109. /**
  110. * @var array
  111. * @access private
  112. */
  113. var $_errorStack = array();
  114. /**
  115. * @var boolean
  116. * @access private
  117. */
  118. var $_internalDownload = false;
  119. /**
  120. * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
  121. * @var array
  122. * @access private
  123. */
  124. var $_packageSortTree;
  125. /**
  126. * Temporary directory, or configuration value where downloads will occur
  127. * @var string
  128. */
  129. var $_downloadDir;
  130. /**
  131. * @param PEAR_Frontend_*
  132. * @param array
  133. * @param PEAR_Config
  134. */
  135. function PEAR_Downloader(&$ui, $options, &$config)
  136. {
  137. parent::PEAR_Common();
  138. $this->_options = $options;
  139. $this->config = &$config;
  140. $this->_preferredState = $this->config->get('preferred_state');
  141. $this->ui = &$ui;
  142. if (!$this->_preferredState) {
  143. // don't inadvertantly use a non-set preferred_state
  144. $this->_preferredState = null;
  145. }
  146. if (isset($this->_options['installroot'])) {
  147. $this->config->setInstallRoot($this->_options['installroot']);
  148. }
  149. $this->_registry = &$config->getRegistry();
  150. if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  151. $this->_installed = $this->_registry->listAllPackages();
  152. foreach ($this->_installed as $key => $unused) {
  153. if (!count($unused)) {
  154. continue;
  155. }
  156. $strtolower = create_function('$a','return strtolower($a);');
  157. array_walk($this->_installed[$key], $strtolower);
  158. }
  159. }
  160. }
  161. /**
  162. * Attempt to discover a channel's remote capabilities from
  163. * its server name
  164. * @param string
  165. * @return boolean
  166. */
  167. function discover($channel)
  168. {
  169. $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
  170. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  171. $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
  172. if (!class_exists('System')) {
  173. require_once 'System.php';
  174. }
  175. $tmp = System::mktemp(array('-d'));
  176. $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
  177. PEAR::popErrorHandling();
  178. if (PEAR::isError($a)) {
  179. // Attempt to fallback to https automatically.
  180. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  181. $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...');
  182. $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
  183. PEAR::popErrorHandling();
  184. if (PEAR::isError($a)) {
  185. return false;
  186. }
  187. }
  188. list($a, $lastmodified) = $a;
  189. if (!class_exists('PEAR_ChannelFile')) {
  190. require_once 'PEAR/ChannelFile.php';
  191. }
  192. $b = new PEAR_ChannelFile;
  193. if ($b->fromXmlFile($a)) {
  194. unlink($a);
  195. if ($this->config->get('auto_discover')) {
  196. $this->_registry->addChannel($b, $lastmodified);
  197. $alias = $b->getName();
  198. if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
  199. $alias = $b->getAlias();
  200. }
  201. $this->log(1, 'Auto-discovered channel "' . $channel .
  202. '", alias "' . $alias . '", adding to registry');
  203. }
  204. return true;
  205. }
  206. unlink($a);
  207. return false;
  208. }
  209. /**
  210. * For simpler unit-testing
  211. * @param PEAR_Downloader
  212. * @return PEAR_Downloader_Package
  213. */
  214. function &newDownloaderPackage(&$t)
  215. {
  216. if (!class_exists('PEAR_Downloader_Package')) {
  217. require_once 'PEAR/Downloader/Package.php';
  218. }
  219. $a = &new PEAR_Downloader_Package($t);
  220. return $a;
  221. }
  222. /**
  223. * For simpler unit-testing
  224. * @param PEAR_Config
  225. * @param array
  226. * @param array
  227. * @param int
  228. */
  229. function &getDependency2Object(&$c, $i, $p, $s)
  230. {
  231. if (!class_exists('PEAR_Dependency2')) {
  232. require_once 'PEAR/Dependency2.php';
  233. }
  234. $z = &new PEAR_Dependency2($c, $i, $p, $s);
  235. return $z;
  236. }
  237. function &download($params)
  238. {
  239. if (!count($params)) {
  240. $a = array();
  241. return $a;
  242. }
  243. if (!isset($this->_registry)) {
  244. $this->_registry = &$this->config->getRegistry();
  245. }
  246. $channelschecked = array();
  247. // convert all parameters into PEAR_Downloader_Package objects
  248. foreach ($params as $i => $param) {
  249. $params[$i] = &$this->newDownloaderPackage($this);
  250. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  251. $err = $params[$i]->initialize($param);
  252. PEAR::staticPopErrorHandling();
  253. if (!$err) {
  254. // skip parameters that were missed by preferred_state
  255. continue;
  256. }
  257. if (PEAR::isError($err)) {
  258. if (!isset($this->_options['soft']) && $err->getMessage() !== '') {
  259. $this->log(0, $err->getMessage());
  260. }
  261. $params[$i] = false;
  262. if (is_object($param)) {
  263. $param = $param->getChannel() . '/' . $param->getPackage();
  264. }
  265. if (!isset($this->_options['soft'])) {
  266. $this->log(2, 'Package "' . $param . '" is not valid');
  267. }
  268. // Message logged above in a specific verbose mode, passing null to not show up on CLI
  269. $this->pushError(null, PEAR_INSTALLER_SKIPPED);
  270. } else {
  271. do {
  272. if ($params[$i] && $params[$i]->getType() == 'local') {
  273. // bug #7090 skip channel.xml check for local packages
  274. break;
  275. }
  276. if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
  277. !isset($this->_options['offline'])
  278. ) {
  279. $channelschecked[$params[$i]->getChannel()] = true;
  280. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  281. if (!class_exists('System')) {
  282. require_once 'System.php';
  283. }
  284. $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
  285. if (PEAR::isError($curchannel)) {
  286. PEAR::staticPopErrorHandling();
  287. return $this->raiseError($curchannel);
  288. }
  289. if (PEAR::isError($dir = $this->getDownloadDir())) {
  290. PEAR::staticPopErrorHandling();
  291. break;
  292. }
  293. $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel());
  294. $url = 'http://' . $mirror . '/channel.xml';
  295. $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified());
  296. PEAR::staticPopErrorHandling();
  297. if (PEAR::isError($a) || !$a) {
  298. // Attempt fallback to https automatically
  299. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  300. $a = $this->downloadHttp('https://' . $mirror .
  301. '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
  302. PEAR::staticPopErrorHandling();
  303. if (PEAR::isError($a) || !$a) {
  304. break;
  305. }
  306. }
  307. $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
  308. 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() .
  309. '" to update');
  310. }
  311. } while (false);
  312. if ($params[$i] && !isset($this->_options['downloadonly'])) {
  313. if (isset($this->_options['packagingroot'])) {
  314. $checkdir = $this->_prependPath(
  315. $this->config->get('php_dir', null, $params[$i]->getChannel()),
  316. $this->_options['packagingroot']);
  317. } else {
  318. $checkdir = $this->config->get('php_dir',
  319. null, $params[$i]->getChannel());
  320. }
  321. while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
  322. $checkdir = dirname($checkdir);
  323. }
  324. if ($checkdir == '.') {
  325. $checkdir = '/';
  326. }
  327. if (!is_writeable($checkdir)) {
  328. return PEAR::raiseError('Cannot install, php_dir for channel "' .
  329. $params[$i]->getChannel() . '" is not writeable by the current user');
  330. }
  331. }
  332. }
  333. }
  334. unset($channelschecked);
  335. PEAR_Downloader_Package::removeDuplicates($params);
  336. if (!count($params)) {
  337. $a = array();
  338. return $a;
  339. }
  340. if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
  341. $reverify = true;
  342. while ($reverify) {
  343. $reverify = false;
  344. foreach ($params as $i => $param) {
  345. //PHP Bug 40768 / PEAR Bug #10944
  346. //Nested foreaches fail in PHP 5.2.1
  347. key($params);
  348. $ret = $params[$i]->detectDependencies($params);
  349. if (PEAR::isError($ret)) {
  350. $reverify = true;
  351. $params[$i] = false;
  352. PEAR_Downloader_Package::removeDuplicates($params);
  353. if (!isset($this->_options['soft'])) {
  354. $this->log(0, $ret->getMessage());
  355. }
  356. continue 2;
  357. }
  358. }
  359. }
  360. }
  361. if (isset($this->_options['offline'])) {
  362. $this->log(3, 'Skipping dependency download check, --offline specified');
  363. }
  364. if (!count($params)) {
  365. $a = array();
  366. return $a;
  367. }
  368. while (PEAR_Downloader_Package::mergeDependencies($params));
  369. PEAR_Downloader_Package::removeDuplicates($params, true);
  370. $errorparams = array();
  371. if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
  372. if (count($errorparams)) {
  373. foreach ($errorparams as $param) {
  374. $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
  375. $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
  376. }
  377. $a = array();
  378. return $a;
  379. }
  380. }
  381. PEAR_Downloader_Package::removeInstalled($params);
  382. if (!count($params)) {
  383. $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  384. $a = array();
  385. return $a;
  386. }
  387. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  388. $err = $this->analyzeDependencies($params);
  389. PEAR::popErrorHandling();
  390. if (!count($params)) {
  391. $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  392. $a = array();
  393. return $a;
  394. }
  395. $ret = array();
  396. $newparams = array();
  397. if (isset($this->_options['pretend'])) {
  398. return $params;
  399. }
  400. $somefailed = false;
  401. foreach ($params as $i => $package) {
  402. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  403. $pf = &$params[$i]->download();
  404. PEAR::staticPopErrorHandling();
  405. if (PEAR::isError($pf)) {
  406. if (!isset($this->_options['soft'])) {
  407. $this->log(1, $pf->getMessage());
  408. $this->log(0, 'Error: cannot download "' .
  409. $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
  410. true) .
  411. '"');
  412. }
  413. $somefailed = true;
  414. continue;
  415. }
  416. $newparams[] = &$params[$i];
  417. $ret[] = array(
  418. 'file' => $pf->getArchiveFile(),
  419. 'info' => &$pf,
  420. 'pkg' => $pf->getPackage()
  421. );
  422. }
  423. if ($somefailed) {
  424. // remove params that did not download successfully
  425. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  426. $err = $this->analyzeDependencies($newparams, true);
  427. PEAR::popErrorHandling();
  428. if (!count($newparams)) {
  429. $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
  430. $a = array();
  431. return $a;
  432. }
  433. }
  434. $this->_downloadedPackages = $ret;
  435. return $newparams;
  436. }
  437. /**
  438. * @param array all packages to be installed
  439. */
  440. function analyzeDependencies(&$params, $force = false)
  441. {
  442. $hasfailed = $failed = false;
  443. if (isset($this->_options['downloadonly'])) {
  444. return;
  445. }
  446. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  447. $redo = true;
  448. $reset = false;
  449. while ($redo) {
  450. $redo = false;
  451. foreach ($params as $i => $param) {
  452. $deps = $param->getDeps();
  453. if (!$deps) {
  454. $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  455. $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  456. $send = $param->getPackageFile();
  457. $installcheck = $depchecker->validatePackage($send, $this, $params);
  458. if (PEAR::isError($installcheck)) {
  459. if (!isset($this->_options['soft'])) {
  460. $this->log(0, $installcheck->getMessage());
  461. }
  462. $hasfailed = true;
  463. $params[$i] = false;
  464. $reset = true;
  465. $redo = true;
  466. $failed = false;
  467. PEAR_Downloader_Package::removeDuplicates($params);
  468. continue 2;
  469. }
  470. continue;
  471. }
  472. if (!$reset && $param->alreadyValidated() && !$force) {
  473. continue;
  474. }
  475. if (count($deps)) {
  476. $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  477. $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  478. $send = $param->getPackageFile();
  479. if ($send === null) {
  480. $send = $param->getDownloadURL();
  481. }
  482. $installcheck = $depchecker->validatePackage($send, $this, $params);
  483. if (PEAR::isError($installcheck)) {
  484. if (!isset($this->_options['soft'])) {
  485. $this->log(0, $installcheck->getMessage());
  486. }
  487. $hasfailed = true;
  488. $params[$i] = false;
  489. $reset = true;
  490. $redo = true;
  491. $failed = false;
  492. PEAR_Downloader_Package::removeDuplicates($params);
  493. continue 2;
  494. }
  495. $failed = false;
  496. if (isset($deps['required'])) {
  497. foreach ($deps['required'] as $type => $dep) {
  498. // note: Dependency2 will never return a PEAR_Error if ignore-errors
  499. // is specified, so soft is needed to turn off logging
  500. if (!isset($dep[0])) {
  501. if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
  502. true, $params))) {
  503. $failed = true;
  504. if (!isset($this->_options['soft'])) {
  505. $this->log(0, $e->getMessage());
  506. }
  507. } elseif (is_array($e) && !$param->alreadyValidated()) {
  508. if (!isset($this->_options['soft'])) {
  509. $this->log(0, $e[0]);
  510. }
  511. }
  512. } else {
  513. foreach ($dep as $d) {
  514. if (PEAR::isError($e =
  515. $depchecker->{"validate{$type}Dependency"}($d,
  516. true, $params))) {
  517. $failed = true;
  518. if (!isset($this->_options['soft'])) {
  519. $this->log(0, $e->getMessage());
  520. }
  521. } elseif (is_array($e) && !$param->alreadyValidated()) {
  522. if (!isset($this->_options['soft'])) {
  523. $this->log(0, $e[0]);
  524. }
  525. }
  526. }
  527. }
  528. }
  529. if (isset($deps['optional'])) {
  530. foreach ($deps['optional'] as $type => $dep) {
  531. if (!isset($dep[0])) {
  532. if (PEAR::isError($e =
  533. $depchecker->{"validate{$type}Dependency"}($dep,
  534. false, $params))) {
  535. $failed = true;
  536. if (!isset($this->_options['soft'])) {
  537. $this->log(0, $e->getMessage());
  538. }
  539. } elseif (is_array($e) && !$param->alreadyValidated()) {
  540. if (!isset($this->_options['soft'])) {
  541. $this->log(0, $e[0]);
  542. }
  543. }
  544. } else {
  545. foreach ($dep as $d) {
  546. if (PEAR::isError($e =
  547. $depchecker->{"validate{$type}Dependency"}($d,
  548. false, $params))) {
  549. $failed = true;
  550. if (!isset($this->_options['soft'])) {
  551. $this->log(0, $e->getMessage());
  552. }
  553. } elseif (is_array($e) && !$param->alreadyValidated()) {
  554. if (!isset($this->_options['soft'])) {
  555. $this->log(0, $e[0]);
  556. }
  557. }
  558. }
  559. }
  560. }
  561. }
  562. $groupname = $param->getGroup();
  563. if (isset($deps['group']) && $groupname) {
  564. if (!isset($deps['group'][0])) {
  565. $deps['group'] = array($deps['group']);
  566. }
  567. $found = false;
  568. foreach ($deps['group'] as $group) {
  569. if ($group['attribs']['name'] == $groupname) {
  570. $found = true;
  571. break;
  572. }
  573. }
  574. if ($found) {
  575. unset($group['attribs']);
  576. foreach ($group as $type => $dep) {
  577. if (!isset($dep[0])) {
  578. if (PEAR::isError($e =
  579. $depchecker->{"validate{$type}Dependency"}($dep,
  580. false, $params))) {
  581. $failed = true;
  582. if (!isset($this->_options['soft'])) {
  583. $this->log(0, $e->getMessage());
  584. }
  585. } elseif (is_array($e) && !$param->alreadyValidated()) {
  586. if (!isset($this->_options['soft'])) {
  587. $this->log(0, $e[0]);
  588. }
  589. }
  590. } else {
  591. foreach ($dep as $d) {
  592. if (PEAR::isError($e =
  593. $depchecker->{"validate{$type}Dependency"}($d,
  594. false, $params))) {
  595. $failed = true;
  596. if (!isset($this->_options['soft'])) {
  597. $this->log(0, $e->getMessage());
  598. }
  599. } elseif (is_array($e) && !$param->alreadyValidated()) {
  600. if (!isset($this->_options['soft'])) {
  601. $this->log(0, $e[0]);
  602. }
  603. }
  604. }
  605. }
  606. }
  607. }
  608. }
  609. } else {
  610. foreach ($deps as $dep) {
  611. if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
  612. $failed = true;
  613. if (!isset($this->_options['soft'])) {
  614. $this->log(0, $e->getMessage());
  615. }
  616. } elseif (is_array($e) && !$param->alreadyValidated()) {
  617. if (!isset($this->_options['soft'])) {
  618. $this->log(0, $e[0]);
  619. }
  620. }
  621. }
  622. }
  623. $params[$i]->setValidated();
  624. }
  625. if ($failed) {
  626. $hasfailed = true;
  627. $params[$i] = false;
  628. $reset = true;
  629. $redo = true;
  630. $failed = false;
  631. PEAR_Downloader_Package::removeDuplicates($params);
  632. continue 2;
  633. }
  634. }
  635. }
  636. PEAR::staticPopErrorHandling();
  637. if ($hasfailed && (isset($this->_options['ignore-errors']) ||
  638. isset($this->_options['nodeps']))) {
  639. // this is probably not needed, but just in case
  640. if (!isset($this->_options['soft'])) {
  641. $this->log(0, 'WARNING: dependencies failed');
  642. }
  643. }
  644. }
  645. /**
  646. * Retrieve the directory that downloads will happen in
  647. * @access private
  648. * @return string
  649. */
  650. function getDownloadDir()
  651. {
  652. if (isset($this->_downloadDir)) {
  653. return $this->_downloadDir;
  654. }
  655. $downloaddir = $this->config->get('download_dir');
  656. if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
  657. if (is_dir($downloaddir) && !is_writable($downloaddir)) {
  658. $this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
  659. '" is not writeable. Change download_dir config variable to ' .
  660. 'a writeable dir to avoid this warning');
  661. }
  662. if (!class_exists('System')) {
  663. require_once 'System.php';
  664. }
  665. if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
  666. return $downloaddir;
  667. }
  668. $this->log(3, '+ tmp dir created at ' . $downloaddir);
  669. }
  670. if (!is_writable($downloaddir)) {
  671. if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
  672. !is_writable($downloaddir)) {
  673. return PEAR::raiseError('download directory "' . $downloaddir .
  674. '" is not writeable. Change download_dir config variable to ' .
  675. 'a writeable dir');
  676. }
  677. }
  678. return $this->_downloadDir = $downloaddir;
  679. }
  680. function setDownloadDir($dir)
  681. {
  682. if (!@is_writable($dir)) {
  683. if (PEAR::isError(System::mkdir(array('-p', $dir)))) {
  684. return PEAR::raiseError('download directory "' . $dir .
  685. '" is not writeable. Change download_dir config variable to ' .
  686. 'a writeable dir');
  687. }
  688. }
  689. $this->_downloadDir = $dir;
  690. }
  691. function configSet($key, $value, $layer = 'user', $channel = false)
  692. {
  693. $this->config->set($key, $value, $layer, $channel);
  694. $this->_preferredState = $this->config->get('preferred_state', null, $channel);
  695. if (!$this->_preferredState) {
  696. // don't inadvertantly use a non-set preferred_state
  697. $this->_preferredState = null;
  698. }
  699. }
  700. function setOptions($options)
  701. {
  702. $this->_options = $options;
  703. }
  704. // }}}
  705. // {{{ setOptions()
  706. function getOptions()
  707. {
  708. return $this->_options;
  709. }
  710. /**
  711. * For simpler unit-testing
  712. * @param PEAR_Config
  713. * @param int
  714. * @param string
  715. */
  716. function &getPackagefileObject(&$c, $d, $t = false)
  717. {
  718. if (!class_exists('PEAR_PackageFile')) {
  719. require_once 'PEAR/PackageFile.php';
  720. }
  721. $a = &new PEAR_PackageFile($c, $d, $t);
  722. return $a;
  723. }
  724. /**
  725. * @param array output of {@link parsePackageName()}
  726. * @access private
  727. */
  728. function _getPackageDownloadUrl($parr)
  729. {
  730. $curchannel = $this->config->get('default_channel');
  731. $this->configSet('default_channel', $parr['channel']);
  732. // getDownloadURL returns an array. On error, it only contains information
  733. // on the latest release as array(version, info). On success it contains
  734. // array(version, info, download url string)
  735. $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  736. if (!$this->_registry->channelExists($parr['channel'])) {
  737. do {
  738. if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) {
  739. break;
  740. }
  741. $this->configSet('default_channel', $curchannel);
  742. return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']);
  743. } while (false);
  744. }
  745. $chan = &$this->_registry->getChannel($parr['channel']);
  746. if (PEAR::isError($chan)) {
  747. return $chan;
  748. }
  749. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  750. $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']);
  751. $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']);
  752. // package is installed - use the installed release stability level
  753. if (!isset($parr['state']) && $stability !== null) {
  754. $state = $stability['release'];
  755. }
  756. PEAR::staticPopErrorHandling();
  757. $base2 = false;
  758. $preferred_mirror = $this->config->get('preferred_mirror');
  759. if (!$chan->supportsREST($preferred_mirror) ||
  760. (
  761. !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
  762. &&
  763. !($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
  764. )
  765. ) {
  766. return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
  767. }
  768. if ($base2) {
  769. $rest = &$this->config->getREST('1.3', $this->_options);
  770. $base = $base2;
  771. } else {
  772. $rest = &$this->config->getREST('1.0', $this->_options);
  773. }
  774. $downloadVersion = false;
  775. if (!isset($parr['version']) && !isset($parr['state']) && $version
  776. && !PEAR::isError($version)
  777. && !isset($this->_options['downloadonly'])
  778. ) {
  779. $downloadVersion = $version;
  780. }
  781. $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName());
  782. if (PEAR::isError($url)) {
  783. $this->configSet('default_channel', $curchannel);
  784. return $url;
  785. }
  786. if ($parr['channel'] != $curchannel) {
  787. $this->configSet('default_channel', $curchannel);
  788. }
  789. if (!is_array($url)) {
  790. return $url;
  791. }
  792. $url['raw'] = false; // no checking is necessary for REST
  793. if (!is_array($url['info'])) {
  794. return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  795. 'this should never happen');
  796. }
  797. if (!isset($this->_options['force']) &&
  798. !isset($this->_options['downloadonly']) &&
  799. $version &&
  800. !PEAR::isError($version) &&
  801. !isset($parr['group'])
  802. ) {
  803. if (version_compare($version, $url['version'], '=')) {
  804. return PEAR::raiseError($this->_registry->parsedPackageNameToString(
  805. $parr, true) . ' is already installed and is the same as the ' .
  806. 'released version ' . $url['version'], -976);
  807. }
  808. if (version_compare($version, $url['version'], '>')) {
  809. return PEAR::raiseError($this->_registry->parsedPackageNameToString(
  810. $parr, true) . ' is already installed and is newer than detected ' .
  811. 'released version ' . $url['version'], -976);
  812. }
  813. }
  814. if (isset($url['info']['required']) || $url['compatible']) {
  815. require_once 'PEAR/PackageFile/v2.php';
  816. $pf = new PEAR_PackageFile_v2;
  817. $pf->setRawChannel($parr['channel']);
  818. if ($url['compatible']) {
  819. $pf->setRawCompatible($url['compatible']);
  820. }
  821. } else {
  822. require_once 'PEAR/PackageFile/v1.php';
  823. $pf = new PEAR_PackageFile_v1;
  824. }
  825. $pf->setRawPackage($url['package']);
  826. $pf->setDeps($url['info']);
  827. if ($url['compatible']) {
  828. $pf->setCompatible($url['compatible']);
  829. }
  830. $pf->setRawState($url['stability']);
  831. $url['info'] = &$pf;
  832. if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  833. $ext = '.tar';
  834. } else {
  835. $ext = '.tgz';
  836. }
  837. if (is_array($url) && isset($url['url'])) {
  838. $url['url'] .= $ext;
  839. }
  840. return $url;
  841. }
  842. /**
  843. * @param array dependency array
  844. * @access private
  845. */
  846. function _getDepPackageDownloadUrl($dep, $parr)
  847. {
  848. $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
  849. $curchannel = $this->config->get('default_channel');
  850. if (isset($dep['uri'])) {
  851. $xsdversion = '2.0';
  852. $chan = &$this->_registry->getChannel('__uri');
  853. if (PEAR::isError($chan)) {
  854. return $chan;
  855. }
  856. $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
  857. $this->configSet('default_channel', '__uri');
  858. } else {
  859. if (isset($dep['channel'])) {
  860. $remotechannel = $dep['channel'];
  861. } else {
  862. $remotechannel = 'pear.php.net';
  863. }
  864. if (!$this->_registry->channelExists($remotechannel)) {
  865. do {
  866. if ($this->config->get('auto_discover')) {
  867. if ($this->discover($remotechannel)) {
  868. break;
  869. }
  870. }
  871. return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  872. } while (false);
  873. }
  874. $chan = &$this->_registry->getChannel($remotechannel);
  875. if (PEAR::isError($chan)) {
  876. return $chan;
  877. }
  878. $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel);
  879. $this->configSet('default_channel', $remotechannel);
  880. }
  881. $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  882. if (isset($parr['state']) && isset($parr['version'])) {
  883. unset($parr['state']);
  884. }
  885. if (isset($dep['uri'])) {
  886. $info = &$this->newDownloaderPackage($this);
  887. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  888. $err = $info->initialize($dep);
  889. PEAR::staticPopErrorHandling();
  890. if (!$err) {
  891. // skip parameters that were missed by preferred_state
  892. return PEAR::raiseError('Cannot initialize dependency');
  893. }
  894. if (PEAR::isError($err)) {
  895. if (!isset($this->_options['soft'])) {
  896. $this->log(0, $err->getMessage());
  897. }
  898. if (is_object($info)) {
  899. $param = $info->getChannel() . '/' . $info->getPackage();
  900. }
  901. return PEAR::raiseError('Package "' . $param . '" is not valid');
  902. }
  903. return $info;
  904. } elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
  905. && $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))
  906. ) {
  907. $rest = &$this->config->getREST('1.0', $this->_options);
  908. $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
  909. $state, $version, $chan->getName());
  910. if (PEAR::isError($url)) {
  911. return $url;
  912. }
  913. if ($parr['channel'] != $curchannel) {
  914. $this->configSet('default_channel', $curchannel);
  915. }
  916. if (!is_array($url)) {
  917. return $url;
  918. }
  919. $url['raw'] = false; // no checking is necessary for REST
  920. if (!is_array($url['info'])) {
  921. return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  922. 'this should never happen');
  923. }
  924. if (isset($url['info']['required'])) {
  925. if (!class_exists('PEAR_PackageFile_v2')) {
  926. require_once 'PEAR/PackageFile/v2.php';
  927. }
  928. $pf = new PEAR_PackageFile_v2;
  929. $pf->setRawChannel($remotechannel);
  930. } else {
  931. if (!class_exists('PEAR_PackageFile_v1')) {
  932. require_once 'PEAR/PackageFile/v1.php';
  933. }
  934. $pf = new PEAR_PackageFile_v1;
  935. }
  936. $pf->setRawPackage($url['package']);
  937. $pf->setDeps($url['info']);
  938. if ($url['compatible']) {
  939. $pf->setCompatible($url['compatible']);
  940. }
  941. $pf->setRawState($url['stability']);
  942. $url['info'] = &$pf;
  943. if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  944. $ext = '.tar';
  945. } else {
  946. $ext = '.tgz';
  947. }
  948. if (is_array($url) && isset($url['url'])) {
  949. $url['url'] .= $ext;
  950. }
  951. return $url;
  952. }
  953. return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
  954. }
  955. /**
  956. * @deprecated in favor of _getPackageDownloadUrl
  957. */
  958. function getPackageDownloadUrl($package, $version = null, $channel = false)
  959. {
  960. if ($version) {
  961. $package .= "-$version";
  962. }
  963. if ($this === null || $this->_registry === null) {
  964. $package = "http://pear.php.net/get/$package";
  965. } else {
  966. $chan = $this->_registry->getChannel($channel);
  967. if (PEAR::isError($chan)) {
  968. return '';
  969. }
  970. $package = "http://" . $chan->getServer() . "/get/$package";
  971. }
  972. if (!extension_loaded("zlib")) {
  973. $package .= '?uncompress=yes';
  974. }
  975. return $package;
  976. }
  977. /**
  978. * Retrieve a list of downloaded packages after a call to {@link download()}.
  979. *
  980. * Also resets the list of downloaded packages.
  981. * @return array
  982. */
  983. function getDownloadedPackages()
  984. {
  985. $ret = $this->_downloadedPackages;
  986. $this->_downloadedPackages = array();
  987. $this->_toDownload = array();
  988. return $ret;
  989. }
  990. function _downloadCallback($msg, $params = null)
  991. {
  992. switch ($msg) {
  993. case 'saveas':
  994. $this->log(1, "downloading $params ...");
  995. break;
  996. case 'done':
  997. $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  998. break;
  999. case 'bytesread':
  1000. static $bytes;
  1001. if (empty($bytes)) {
  1002. $bytes = 0;
  1003. }
  1004. if (!($bytes % 10240)) {
  1005. $this->log(1, '.', false);
  1006. }
  1007. $bytes += $params;
  1008. break;
  1009. case 'start':
  1010. if($params[1] == -1) {
  1011. $length = "Unknown size";
  1012. } else {
  1013. $length = number_format($params[1], 0, '', ',')." bytes";
  1014. }
  1015. $this->log(1, "Starting to download {$params[0]} ($length)");
  1016. break;
  1017. }
  1018. if (method_exists($this->ui, '_downloadCallback'))
  1019. $this->ui->_downloadCallback($msg, $params);
  1020. }
  1021. function _prependPath($path, $prepend)
  1022. {
  1023. if (strlen($prepend) > 0) {
  1024. if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  1025. if (preg_match('/^[a-z]:/i', $prepend)) {
  1026. $prepend = substr($prepend, 2);
  1027. } elseif ($prepend{0} != '\\') {
  1028. $prepend = "\\$prepend";
  1029. }
  1030. $path = substr($path, 0, 2) . $prepend . substr($path, 2);
  1031. } else {
  1032. $path = $prepend . $path;
  1033. }
  1034. }
  1035. return $path;
  1036. }
  1037. /**
  1038. * @param string
  1039. * @param integer
  1040. */
  1041. function pushError($errmsg, $code = -1)
  1042. {
  1043. array_push($this->_errorStack, array($errmsg, $code));
  1044. }
  1045. function getErrorMsgs()
  1046. {
  1047. $msgs = array();
  1048. $errs = $this->_errorStack;
  1049. foreach ($errs as $err) {
  1050. $msgs[] = $err[0];
  1051. }
  1052. $this->_errorStack = array();
  1053. return $msgs;
  1054. }
  1055. /**
  1056. * for BC
  1057. *
  1058. * @deprecated
  1059. */
  1060. function sortPkgDeps(&$packages, $uninstall = false)
  1061. {
  1062. $uninstall ?
  1063. $this->sortPackagesForUninstall($packages) :
  1064. $this->sortPackagesForInstall($packages);
  1065. }
  1066. /**
  1067. * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1068. *
  1069. * This uses the topological sort method from graph theory, and the
  1070. * Structures_Graph package to properly sort dependencies for installation.
  1071. * @param array an array of downloaded PEAR_Downloader_Packages
  1072. * @return array array of array(packagefilename, package.xml contents)
  1073. */
  1074. function sortPackagesForInstall(&$packages)
  1075. {
  1076. require_once 'Structures/Graph.php';
  1077. require_once 'Structures/Graph/Node.php';
  1078. require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
  1079. $depgraph = new Structures_Graph(true);
  1080. $nodes = array();
  1081. $reg = &$this->config->getRegistry();
  1082. foreach ($packages as $i => $package) {
  1083. $pname = $reg->parsedPackageNameToString(
  1084. array(
  1085. 'channel' => $package->getChannel(),
  1086. 'package' => strtolower($package->getPackage()),
  1087. ));
  1088. $nodes[$pname] = new Structures_Graph_Node;
  1089. $nodes[$pname]->setData($packages[$i]);
  1090. $depgraph->addNode($nodes[$pname]);
  1091. }
  1092. $deplinks = array();
  1093. foreach ($nodes as $package => $node) {
  1094. $pf = &$node->getData();
  1095. $pdeps = $pf->getDeps(true);
  1096. if (!$pdeps) {
  1097. continue;
  1098. }
  1099. if ($pf->getPackagexmlVersion() == '1.0') {
  1100. foreach ($pdeps as $dep) {
  1101. if ($dep['type'] != 'pkg' ||
  1102. (isset($dep['optional']) && $dep['optional'] == 'yes')) {
  1103. continue;
  1104. }
  1105. $dname = $reg->parsedPackageNameToString(
  1106. array(
  1107. 'channel' => 'pear.php.net',
  1108. 'package' => strtolower($dep['name']),
  1109. ));
  1110. if (isset($nodes[$dname])) {
  1111. if (!isset($deplinks[$dname])) {
  1112. $deplinks[$dname] = array();
  1113. }
  1114. $deplinks[$dname][$package] = 1;
  1115. // dependency is in installed packages
  1116. continue;
  1117. }
  1118. $dname = $reg->parsedPackageNameToString(
  1119. array(
  1120. 'channel' => 'pecl.php.net',
  1121. 'package' => strtolower($dep['name']),
  1122. ));
  1123. if (isset($nodes[$dname])) {
  1124. if (!isset($deplinks[$dname])) {
  1125. $deplinks[$dname] = array();
  1126. }
  1127. $deplinks[$dname][$package] = 1;
  1128. // dependency is in installed packages
  1129. continue;
  1130. }
  1131. }
  1132. } else {
  1133. // the only ordering we care about is:
  1134. // 1) subpackages must be installed before packages that depend on them
  1135. // 2) required deps must be installed before packages that depend on them
  1136. if (isset($pdeps['required']['subpackage'])) {
  1137. $t = $pdeps['required']['subpackage'];
  1138. if (!isset($t[0])) {
  1139. $t = array($t);
  1140. }
  1141. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1142. }
  1143. if (isset($pdeps['group'])) {
  1144. if (!isset($pdeps['group'][0])) {
  1145. $pdeps['group'] = array($pdeps['group']);
  1146. }
  1147. foreach ($pdeps['group'] as $group) {
  1148. if (isset($group['subpackage'])) {
  1149. $t = $group['subpackage'];
  1150. if (!isset($t[0])) {
  1151. $t = array($t);
  1152. }
  1153. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1154. }
  1155. }
  1156. }
  1157. if (isset($pdeps['optional']['subpackage'])) {
  1158. $t = $pdeps['optional']['subpackage'];
  1159. if (!isset($t[0])) {
  1160. $t = array($t);
  1161. }
  1162. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1163. }
  1164. if (isset($pdeps['required']['package'])) {
  1165. $t = $pdeps['required']['package'];
  1166. if (!isset($t[0])) {
  1167. $t = array($t);
  1168. }
  1169. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1170. }
  1171. if (isset($pdeps['group'])) {
  1172. if (!isset($pdeps['group'][0])) {
  1173. $pdeps['group'] = array($pdeps['group']);
  1174. }
  1175. foreach ($pdeps['group'] as $group) {
  1176. if (isset($group['package'])) {
  1177. $t = $group['package'];
  1178. if (!isset($t[0])) {
  1179. $t = array($t);
  1180. }
  1181. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1182. }
  1183. }
  1184. }
  1185. }
  1186. }
  1187. $this->_detectDepCycle($deplinks);
  1188. foreach ($deplinks as $dependent => $parents) {
  1189. foreach ($parents as $parent => $unused) {
  1190. $nodes[$dependent]->connectTo($nodes[$parent]);
  1191. }
  1192. }
  1193. $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
  1194. $ret = array();
  1195. for ($i = 0, $count = co