PageRenderTime 69ms CodeModel.GetById 28ms RepoModel.GetById 0ms app.codeStats 1ms

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

http://gespac.googlecode.com/
PHP | 1735 lines | 1433 code | 116 blank | 186 comment | 235 complexity | 0646fba5c60d4b906889973c8e7ac6ae MD5 | raw 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 = count($installOrder); $i < $count; $i++) {
  1196. foreach ($installOrder[$i] as $index => $sortedpackage) {
  1197. $data = &$installOrder[$i][$index]->getData();
  1198. $ret[] = &$nodes[$reg->parsedPackageNameToString(
  1199. array(
  1200. 'channel' => $data->getChannel(),
  1201. 'package' => strtolower($data->getPackage()),
  1202. ))]->getData();
  1203. }
  1204. }
  1205. $packages = $ret;
  1206. return;
  1207. }
  1208. /**
  1209. * Detect recursive links between dependencies and break the cycles
  1210. *
  1211. * @param array
  1212. * @access private
  1213. */
  1214. function _detectDepCycle(&$deplinks)
  1215. {
  1216. do {
  1217. $keepgoing = false;
  1218. foreach ($deplinks as $dep => $parents) {
  1219. foreach ($parents as $parent => $unused) {
  1220. // reset the parent cycle detector
  1221. $this->_testCycle(null, null, null);
  1222. if ($this->_testCycle($dep, $deplinks, $parent)) {
  1223. $keepgoing = true;
  1224. unset($deplinks[$dep][$parent]);
  1225. if (count($deplinks[$dep]) == 0) {
  1226. unset($deplinks[$dep]);
  1227. }
  1228. continue 3;
  1229. }
  1230. }
  1231. }
  1232. } while ($keepgoing);
  1233. }
  1234. function _testCycle($test, $deplinks, $dep)
  1235. {
  1236. static $visited = array();
  1237. if ($test === null) {
  1238. $visited = array();
  1239. return;
  1240. }
  1241. // this happens when a parent has a dep cycle on another dependency
  1242. // but the child is not part of the cycle
  1243. if (isset($visited[$dep])) {
  1244. return false;
  1245. }
  1246. $visited[$dep] = 1;
  1247. if ($test == $dep) {
  1248. return true;
  1249. }
  1250. if (isset($deplinks[$dep])) {
  1251. if (in_array($test, array_keys($deplinks[$dep]), true)) {
  1252. return true;
  1253. }
  1254. foreach ($deplinks[$dep] as $parent => $unused) {
  1255. if ($this->_testCycle($test, $deplinks, $parent)) {
  1256. return true;
  1257. }
  1258. }
  1259. }
  1260. return false;
  1261. }
  1262. /**
  1263. * Set up the dependency for installation parsing
  1264. *
  1265. * @param array $t dependency information
  1266. * @param PEAR_Registry $reg
  1267. * @param array $deplinks list of dependency links already established
  1268. * @param array $nodes all existing package nodes
  1269. * @param string $package parent package name
  1270. * @access private
  1271. */
  1272. function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
  1273. {
  1274. foreach ($t as $dep) {
  1275. $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel'];
  1276. $dname = $reg->parsedPackageNameToString(
  1277. array(
  1278. 'channel' => $depchannel,
  1279. 'package' => strtolower($dep['name']),
  1280. ));
  1281. if (isset($nodes[$dname])) {
  1282. if (!isset($deplinks[$dname])) {
  1283. $deplinks[$dname] = array();
  1284. }
  1285. $deplinks[$dname][$package] = 1;
  1286. }
  1287. }
  1288. }
  1289. function _dependsOn($a, $b)
  1290. {
  1291. return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b);
  1292. }
  1293. function _checkDepTree($channel, $package, $b, $checked = array())
  1294. {
  1295. $checked[$channel][$package] = true;
  1296. if (!isset($this->_depTree[$channel][$package])) {
  1297. return false;
  1298. }
  1299. if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
  1300. [strtolower($b->getPackage())])) {
  1301. return true;
  1302. }
  1303. foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
  1304. foreach ($packages as $pa => $true) {
  1305. if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
  1306. return true;
  1307. }
  1308. }
  1309. }
  1310. return false;
  1311. }
  1312. function _sortInstall($a, $b)
  1313. {
  1314. if (!$a->getDeps() && !$b->getDeps()) {
  1315. return 0; // neither package has dependencies, order is insignificant
  1316. }
  1317. if ($a->getDeps() && !$b->getDeps()) {
  1318. return 1; // $a must be installed after $b because $a has dependencies
  1319. }
  1320. if (!$a->getDeps() && $b->getDeps()) {
  1321. return -1; // $b must be installed after $a because $b has dependencies
  1322. }
  1323. // both packages have dependencies
  1324. if ($this->_dependsOn($a, $b)) {
  1325. return 1;
  1326. }
  1327. if ($this->_dependsOn($b, $a)) {
  1328. return -1;
  1329. }
  1330. return 0;
  1331. }
  1332. /**
  1333. * Download a file through HTTP. Considers suggested file name in
  1334. * Content-disposition: header and can run a callback function for
  1335. * different events. The callback will be called with two
  1336. * parameters: the callback type, and parameters. The implemented
  1337. * callback types are:
  1338. *
  1339. * 'setup' called at the very beginning, parameter is a UI object
  1340. * that should be used for all output
  1341. * 'message' the parameter is a string with an informational message
  1342. * 'saveas' may be used to save with a different file name, the
  1343. * parameter is the filename that is about to be used.
  1344. * If a 'saveas' callback returns a non-empty string,
  1345. * that file name will be used as the filename instead.
  1346. * Note that $save_dir will not be affected by this, only
  1347. * the basename of the file.
  1348. * 'start' download is starting, parameter is number of bytes
  1349. * that are expected, or -1 if unknown
  1350. * 'bytesread' parameter is the number of bytes read so far
  1351. * 'done' download is complete, parameter is the total number
  1352. * of bytes read
  1353. * 'connfailed' if the TCP/SSL connection fails, this callback is called
  1354. * with array(host,port,errno,errmsg)
  1355. * 'writefailed' if writing to disk fails, this callback is called
  1356. * with array(destfile,errmsg)
  1357. *
  1358. * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1359. * setting), the proxy will be used.
  1360. *
  1361. * @param string $url the URL to download
  1362. * @param object $ui PEAR_Frontend_* instance
  1363. * @param object $config PEAR_Config instance
  1364. * @param string $save_dir directory to save file in
  1365. * @param mixed $callback function/method to call for status
  1366. * updates
  1367. * @param false|string|array $lastmodified header values to check against for caching
  1368. * use false to return the header values from this download
  1369. * @param false|array $accept Accept headers to send
  1370. * @param false|string $channel Channel to use for retrieving authentication
  1371. * @return string|array Returns the full path of the downloaded file or a PEAR
  1372. * error on failure. If the error is caused by
  1373. * socket-related errors, the error object will
  1374. * have the fsockopen error code available through
  1375. * getCode(). If caching is requested, then return the header
  1376. * values.
  1377. *
  1378. * @access public
  1379. */
  1380. function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
  1381. $accept = false, $channel = false)
  1382. {
  1383. static $redirect = 0;
  1384. // always reset , so we are clean case of error
  1385. $wasredirect = $redirect;
  1386. $redirect = 0;
  1387. if ($callback) {
  1388. call_user_func($callback, 'setup', array(&$ui));
  1389. }
  1390. $info = parse_url($url);
  1391. if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
  1392. return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
  1393. }
  1394. if (!isset($info['host'])) {
  1395. return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
  1396. }
  1397. $host = isset($info['host']) ? $info['host'] : null;
  1398. $port = isset($info['port']) ? $info['port'] : null;
  1399. $path = isset($info['path']) ? $info['path'] : null;
  1400. if (isset($this)) {
  1401. $config = &$this->config;
  1402. } else {
  1403. $config = &PEAR_Config::singleton();
  1404. }
  1405. $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1406. if ($config->get('http_proxy') &&
  1407. $proxy = parse_url($config->get('http_proxy'))) {
  1408. $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
  1409. if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
  1410. $proxy_host = 'ssl://' . $proxy_host;
  1411. }
  1412. $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
  1413. $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
  1414. $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
  1415. if ($callback) {
  1416. call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1417. }
  1418. }
  1419. if (empty($port)) {
  1420. $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
  1421. }
  1422. $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
  1423. if ($proxy_host != '') {
  1424. $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1425. if (!$fp) {
  1426. if ($callback) {
  1427. call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1428. $errno, $errstr));
  1429. }
  1430. return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1431. }
  1432. if ($lastmodified === false || $lastmodified) {
  1433. $request = "GET $url HTTP/1.1\r\n";
  1434. $request .= "Host: $host\r\n";
  1435. } else {
  1436. $request = "GET $url HTTP/1.0\r\n";
  1437. $request .= "Host: $host\r\n";
  1438. }
  1439. } else {
  1440. $network_host = $host;
  1441. if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1442. $network_host = 'ssl://' . $host;
  1443. }
  1444. $fp = @fsockopen($network_host, $port, $errno, $errstr);
  1445. if (!$fp) {
  1446. if ($callback) {
  1447. call_user_func($callback, 'connfailed', array($host, $port,
  1448. $errno, $errstr));
  1449. }
  1450. return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1451. }
  1452. if ($lastmodified === false || $lastmodified) {
  1453. $request = "GET $path HTTP/1.1\r\n";
  1454. $request .= "Host: $host\r\n";
  1455. } else {
  1456. $request = "GET $path HTTP/1.0\r\n";
  1457. $request .= "Host: $host\r\n";
  1458. }
  1459. }
  1460. $ifmodifiedsince = '';
  1461. if (is_array($lastmodified)) {
  1462. if (isset($lastmodified['Last-Modified'])) {
  1463. $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
  1464. }
  1465. if (isset($lastmodified['ETag'])) {
  1466. $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
  1467. }
  1468. } else {
  1469. $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
  1470. }
  1471. $request .= $ifmodifiedsince .
  1472. "User-Agent: PEAR/1.9.1/PHP/" . PHP_VERSION . "\r\n";
  1473. if (isset($this)) { // only pass in authentication for non-static calls
  1474. $username = $config->get('username', null, $channel);
  1475. $password = $config->get('password', null, $channel);
  1476. if ($username && $password) {
  1477. $tmp = base64_encode("$username:$password");
  1478. $request .= "Authorization: Basic $tmp\r\n";
  1479. }
  1480. }
  1481. if ($proxy_host != '' && $proxy_user != '') {
  1482. $request .= 'Proxy-Authorization: Basic ' .
  1483. base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1484. }
  1485. if ($accept) {
  1486. $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
  1487. }
  1488. $request .= "Connection: close\r\n";
  1489. $request .= "\r\n";
  1490. fwrite($fp, $request);
  1491. $headers = array();
  1492. $reply = 0;
  1493. while (trim($line = fgets($fp, 1024))) {
  1494. if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
  1495. $headers[strtolower($matches[1])] = trim($matches[2]);
  1496. } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1497. $reply = (int)$matches[1];
  1498. if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
  1499. return false;
  1500. }
  1501. if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
  1502. return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)");
  1503. }
  1504. }
  1505. }
  1506. if ($reply != 200) {
  1507. if (!isset($headers['location'])) {
  1508. return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)");
  1509. }
  1510. if ($wasredirect > 4) {
  1511. return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)");
  1512. }
  1513. $redirect = $wasredirect + 1;
  1514. return $this->downloadHttp($headers['location'],
  1515. $ui, $save_dir, $callback, $lastmodified, $accept);
  1516. }
  1517. if (isset($headers['content-disposition']) &&
  1518. preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
  1519. $save_as = basename($matches[1]);
  1520. } else {
  1521. $save_as = basename($url);
  1522. }
  1523. if ($callback) {
  1524. $tmp = call_user_func($callback, 'saveas', $save_as);
  1525. if ($tmp) {
  1526. $save_as = $tmp;
  1527. }
  1528. }
  1529. $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1530. if (!$wp = @fopen($dest_file, 'wb')) {
  1531. fclose($fp);
  1532. if ($callback) {
  1533. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1534. }
  1535. return PEAR::raiseError("could not open $dest_file for writing");
  1536. }
  1537. $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
  1538. $bytes = 0;
  1539. if ($callback) {
  1540. call_user_func($callback, 'start', array(basename($dest_file), $length));
  1541. }
  1542. while ($data = fread($fp, 1024)) {
  1543. $bytes += strlen($data);
  1544. if ($callback) {
  1545. call_user_func($callback, 'bytesread', $bytes);
  1546. }
  1547. if (!@fwrite($wp, $data)) {
  1548. fclose($fp);
  1549. if ($callback) {
  1550. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1551. }
  1552. return PEAR::raiseError("$dest_file: write failed ($ph