PageRenderTime 60ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/php/PEAR/Downloader.php

https://bitbucket.org/adarshj/convenient_website
PHP | 1766 lines | 1465 code | 123 blank | 178 comment | 237 complexity | 3f62de6899c2ca37c89810e3510b77b9 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-2-Clause, GPL-2.0, LGPL-3.0
  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 313024 2011-07-06 19:51:24Z 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.4
  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. $tmpdir = $this->config->get('temp_dir');
  176. $tmp = System::mktemp('-d -t "' . $tmpdir . '"');
  177. $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
  178. PEAR::popErrorHandling();
  179. if (PEAR::isError($a)) {
  180. // Attempt to fallback to https automatically.
  181. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  182. $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...');
  183. $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
  184. PEAR::popErrorHandling();
  185. if (PEAR::isError($a)) {
  186. return false;
  187. }
  188. }
  189. list($a, $lastmodified) = $a;
  190. if (!class_exists('PEAR_ChannelFile')) {
  191. require_once 'PEAR/ChannelFile.php';
  192. }
  193. $b = new PEAR_ChannelFile;
  194. if ($b->fromXmlFile($a)) {
  195. unlink($a);
  196. if ($this->config->get('auto_discover')) {
  197. $this->_registry->addChannel($b, $lastmodified);
  198. $alias = $b->getName();
  199. if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
  200. $alias = $b->getAlias();
  201. }
  202. $this->log(1, 'Auto-discovered channel "' . $channel .
  203. '", alias "' . $alias . '", adding to registry');
  204. }
  205. return true;
  206. }
  207. unlink($a);
  208. return false;
  209. }
  210. /**
  211. * For simpler unit-testing
  212. * @param PEAR_Downloader
  213. * @return PEAR_Downloader_Package
  214. */
  215. function &newDownloaderPackage(&$t)
  216. {
  217. if (!class_exists('PEAR_Downloader_Package')) {
  218. require_once 'PEAR/Downloader/Package.php';
  219. }
  220. $a = &new PEAR_Downloader_Package($t);
  221. return $a;
  222. }
  223. /**
  224. * For simpler unit-testing
  225. * @param PEAR_Config
  226. * @param array
  227. * @param array
  228. * @param int
  229. */
  230. function &getDependency2Object(&$c, $i, $p, $s)
  231. {
  232. if (!class_exists('PEAR_Dependency2')) {
  233. require_once 'PEAR/Dependency2.php';
  234. }
  235. $z = &new PEAR_Dependency2($c, $i, $p, $s);
  236. return $z;
  237. }
  238. function &download($params)
  239. {
  240. if (!count($params)) {
  241. $a = array();
  242. return $a;
  243. }
  244. if (!isset($this->_registry)) {
  245. $this->_registry = &$this->config->getRegistry();
  246. }
  247. $channelschecked = array();
  248. // convert all parameters into PEAR_Downloader_Package objects
  249. foreach ($params as $i => $param) {
  250. $params[$i] = &$this->newDownloaderPackage($this);
  251. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  252. $err = $params[$i]->initialize($param);
  253. PEAR::staticPopErrorHandling();
  254. if (!$err) {
  255. // skip parameters that were missed by preferred_state
  256. continue;
  257. }
  258. if (PEAR::isError($err)) {
  259. if (!isset($this->_options['soft']) && $err->getMessage() !== '') {
  260. $this->log(0, $err->getMessage());
  261. }
  262. $params[$i] = false;
  263. if (is_object($param)) {
  264. $param = $param->getChannel() . '/' . $param->getPackage();
  265. }
  266. if (!isset($this->_options['soft'])) {
  267. $this->log(2, 'Package "' . $param . '" is not valid');
  268. }
  269. // Message logged above in a specific verbose mode, passing null to not show up on CLI
  270. $this->pushError(null, PEAR_INSTALLER_SKIPPED);
  271. } else {
  272. do {
  273. if ($params[$i] && $params[$i]->getType() == 'local') {
  274. // bug #7090 skip channel.xml check for local packages
  275. break;
  276. }
  277. if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
  278. !isset($this->_options['offline'])
  279. ) {
  280. $channelschecked[$params[$i]->getChannel()] = true;
  281. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  282. if (!class_exists('System')) {
  283. require_once 'System.php';
  284. }
  285. $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
  286. if (PEAR::isError($curchannel)) {
  287. PEAR::staticPopErrorHandling();
  288. return $this->raiseError($curchannel);
  289. }
  290. if (PEAR::isError($dir = $this->getDownloadDir())) {
  291. PEAR::staticPopErrorHandling();
  292. break;
  293. }
  294. $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel());
  295. $url = 'http://' . $mirror . '/channel.xml';
  296. $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified());
  297. PEAR::staticPopErrorHandling();
  298. if (PEAR::isError($a) || !$a) {
  299. // Attempt fallback to https automatically
  300. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  301. $a = $this->downloadHttp('https://' . $mirror .
  302. '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
  303. PEAR::staticPopErrorHandling();
  304. if (PEAR::isError($a) || !$a) {
  305. break;
  306. }
  307. }
  308. $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
  309. 'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() .
  310. '" to update');
  311. }
  312. } while (false);
  313. if ($params[$i] && !isset($this->_options['downloadonly'])) {
  314. if (isset($this->_options['packagingroot'])) {
  315. $checkdir = $this->_prependPath(
  316. $this->config->get('php_dir', null, $params[$i]->getChannel()),
  317. $this->_options['packagingroot']);
  318. } else {
  319. $checkdir = $this->config->get('php_dir',
  320. null, $params[$i]->getChannel());
  321. }
  322. while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
  323. $checkdir = dirname($checkdir);
  324. }
  325. if ($checkdir == '.') {
  326. $checkdir = '/';
  327. }
  328. if (!is_writeable($checkdir)) {
  329. return PEAR::raiseError('Cannot install, php_dir for channel "' .
  330. $params[$i]->getChannel() . '" is not writeable by the current user');
  331. }
  332. }
  333. }
  334. }
  335. unset($channelschecked);
  336. PEAR_Downloader_Package::removeDuplicates($params);
  337. if (!count($params)) {
  338. $a = array();
  339. return $a;
  340. }
  341. if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
  342. $reverify = true;
  343. while ($reverify) {
  344. $reverify = false;
  345. foreach ($params as $i => $param) {
  346. //PHP Bug 40768 / PEAR Bug #10944
  347. //Nested foreaches fail in PHP 5.2.1
  348. key($params);
  349. $ret = $params[$i]->detectDependencies($params);
  350. if (PEAR::isError($ret)) {
  351. $reverify = true;
  352. $params[$i] = false;
  353. PEAR_Downloader_Package::removeDuplicates($params);
  354. if (!isset($this->_options['soft'])) {
  355. $this->log(0, $ret->getMessage());
  356. }
  357. continue 2;
  358. }
  359. }
  360. }
  361. }
  362. if (isset($this->_options['offline'])) {
  363. $this->log(3, 'Skipping dependency download check, --offline specified');
  364. }
  365. if (!count($params)) {
  366. $a = array();
  367. return $a;
  368. }
  369. while (PEAR_Downloader_Package::mergeDependencies($params));
  370. PEAR_Downloader_Package::removeDuplicates($params, true);
  371. $errorparams = array();
  372. if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
  373. if (count($errorparams)) {
  374. foreach ($errorparams as $param) {
  375. $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
  376. $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
  377. }
  378. $a = array();
  379. return $a;
  380. }
  381. }
  382. PEAR_Downloader_Package::removeInstalled($params);
  383. if (!count($params)) {
  384. $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  385. $a = array();
  386. return $a;
  387. }
  388. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  389. $err = $this->analyzeDependencies($params);
  390. PEAR::popErrorHandling();
  391. if (!count($params)) {
  392. $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  393. $a = array();
  394. return $a;
  395. }
  396. $ret = array();
  397. $newparams = array();
  398. if (isset($this->_options['pretend'])) {
  399. return $params;
  400. }
  401. $somefailed = false;
  402. foreach ($params as $i => $package) {
  403. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  404. $pf = &$params[$i]->download();
  405. PEAR::staticPopErrorHandling();
  406. if (PEAR::isError($pf)) {
  407. if (!isset($this->_options['soft'])) {
  408. $this->log(1, $pf->getMessage());
  409. $this->log(0, 'Error: cannot download "' .
  410. $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
  411. true) .
  412. '"');
  413. }
  414. $somefailed = true;
  415. continue;
  416. }
  417. $newparams[] = &$params[$i];
  418. $ret[] = array(
  419. 'file' => $pf->getArchiveFile(),
  420. 'info' => &$pf,
  421. 'pkg' => $pf->getPackage()
  422. );
  423. }
  424. if ($somefailed) {
  425. // remove params that did not download successfully
  426. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  427. $err = $this->analyzeDependencies($newparams, true);
  428. PEAR::popErrorHandling();
  429. if (!count($newparams)) {
  430. $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
  431. $a = array();
  432. return $a;
  433. }
  434. }
  435. $this->_downloadedPackages = $ret;
  436. return $newparams;
  437. }
  438. /**
  439. * @param array all packages to be installed
  440. */
  441. function analyzeDependencies(&$params, $force = false)
  442. {
  443. if (isset($this->_options['downloadonly'])) {
  444. return;
  445. }
  446. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  447. $redo = true;
  448. $reset = $hasfailed = $failed = 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']) && is_array($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']) && is_array($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. function getOptions()
  705. {
  706. return $this->_options;
  707. }
  708. /**
  709. * @param array output of {@link parsePackageName()}
  710. * @access private
  711. */
  712. function _getPackageDownloadUrl($parr)
  713. {
  714. $curchannel = $this->config->get('default_channel');
  715. $this->configSet('default_channel', $parr['channel']);
  716. // getDownloadURL returns an array. On error, it only contains information
  717. // on the latest release as array(version, info). On success it contains
  718. // array(version, info, download url string)
  719. $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  720. if (!$this->_registry->channelExists($parr['channel'])) {
  721. do {
  722. if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) {
  723. break;
  724. }
  725. $this->configSet('default_channel', $curchannel);
  726. return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']);
  727. } while (false);
  728. }
  729. $chan = &$this->_registry->getChannel($parr['channel']);
  730. if (PEAR::isError($chan)) {
  731. return $chan;
  732. }
  733. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  734. $version = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']);
  735. $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']);
  736. // package is installed - use the installed release stability level
  737. if (!isset($parr['state']) && $stability !== null) {
  738. $state = $stability['release'];
  739. }
  740. PEAR::staticPopErrorHandling();
  741. $base2 = false;
  742. $preferred_mirror = $this->config->get('preferred_mirror');
  743. if (!$chan->supportsREST($preferred_mirror) ||
  744. (
  745. !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
  746. &&
  747. !($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
  748. )
  749. ) {
  750. return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
  751. }
  752. if ($base2) {
  753. $rest = &$this->config->getREST('1.3', $this->_options);
  754. $base = $base2;
  755. } else {
  756. $rest = &$this->config->getREST('1.0', $this->_options);
  757. }
  758. $downloadVersion = false;
  759. if (!isset($parr['version']) && !isset($parr['state']) && $version
  760. && !PEAR::isError($version)
  761. && !isset($this->_options['downloadonly'])
  762. ) {
  763. $downloadVersion = $version;
  764. }
  765. $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName());
  766. if (PEAR::isError($url)) {
  767. $this->configSet('default_channel', $curchannel);
  768. return $url;
  769. }
  770. if ($parr['channel'] != $curchannel) {
  771. $this->configSet('default_channel', $curchannel);
  772. }
  773. if (!is_array($url)) {
  774. return $url;
  775. }
  776. $url['raw'] = false; // no checking is necessary for REST
  777. if (!is_array($url['info'])) {
  778. return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  779. 'this should never happen');
  780. }
  781. if (!isset($this->_options['force']) &&
  782. !isset($this->_options['downloadonly']) &&
  783. $version &&
  784. !PEAR::isError($version) &&
  785. !isset($parr['group'])
  786. ) {
  787. if (version_compare($version, $url['version'], '=')) {
  788. return PEAR::raiseError($this->_registry->parsedPackageNameToString(
  789. $parr, true) . ' is already installed and is the same as the ' .
  790. 'released version ' . $url['version'], -976);
  791. }
  792. if (version_compare($version, $url['version'], '>')) {
  793. return PEAR::raiseError($this->_registry->parsedPackageNameToString(
  794. $parr, true) . ' is already installed and is newer than detected ' .
  795. 'released version ' . $url['version'], -976);
  796. }
  797. }
  798. if (isset($url['info']['required']) || $url['compatible']) {
  799. require_once 'PEAR/PackageFile/v2.php';
  800. $pf = new PEAR_PackageFile_v2;
  801. $pf->setRawChannel($parr['channel']);
  802. if ($url['compatible']) {
  803. $pf->setRawCompatible($url['compatible']);
  804. }
  805. } else {
  806. require_once 'PEAR/PackageFile/v1.php';
  807. $pf = new PEAR_PackageFile_v1;
  808. }
  809. $pf->setRawPackage($url['package']);
  810. $pf->setDeps($url['info']);
  811. if ($url['compatible']) {
  812. $pf->setCompatible($url['compatible']);
  813. }
  814. $pf->setRawState($url['stability']);
  815. $url['info'] = &$pf;
  816. if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  817. $ext = '.tar';
  818. } else {
  819. $ext = '.tgz';
  820. }
  821. if (is_array($url) && isset($url['url'])) {
  822. $url['url'] .= $ext;
  823. }
  824. return $url;
  825. }
  826. /**
  827. * @param array dependency array
  828. * @access private
  829. */
  830. function _getDepPackageDownloadUrl($dep, $parr)
  831. {
  832. $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
  833. $curchannel = $this->config->get('default_channel');
  834. if (isset($dep['uri'])) {
  835. $xsdversion = '2.0';
  836. $chan = &$this->_registry->getChannel('__uri');
  837. if (PEAR::isError($chan)) {
  838. return $chan;
  839. }
  840. $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
  841. $this->configSet('default_channel', '__uri');
  842. } else {
  843. if (isset($dep['channel'])) {
  844. $remotechannel = $dep['channel'];
  845. } else {
  846. $remotechannel = 'pear.php.net';
  847. }
  848. if (!$this->_registry->channelExists($remotechannel)) {
  849. do {
  850. if ($this->config->get('auto_discover')) {
  851. if ($this->discover($remotechannel)) {
  852. break;
  853. }
  854. }
  855. return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  856. } while (false);
  857. }
  858. $chan = &$this->_registry->getChannel($remotechannel);
  859. if (PEAR::isError($chan)) {
  860. return $chan;
  861. }
  862. $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel);
  863. $this->configSet('default_channel', $remotechannel);
  864. }
  865. $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  866. if (isset($parr['state']) && isset($parr['version'])) {
  867. unset($parr['state']);
  868. }
  869. if (isset($dep['uri'])) {
  870. $info = &$this->newDownloaderPackage($this);
  871. PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  872. $err = $info->initialize($dep);
  873. PEAR::staticPopErrorHandling();
  874. if (!$err) {
  875. // skip parameters that were missed by preferred_state
  876. return PEAR::raiseError('Cannot initialize dependency');
  877. }
  878. if (PEAR::isError($err)) {
  879. if (!isset($this->_options['soft'])) {
  880. $this->log(0, $err->getMessage());
  881. }
  882. if (is_object($info)) {
  883. $param = $info->getChannel() . '/' . $info->getPackage();
  884. }
  885. return PEAR::raiseError('Package "' . $param . '" is not valid');
  886. }
  887. return $info;
  888. } elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
  889. &&
  890. (
  891. ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror')))
  892. ||
  893. ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')))
  894. )
  895. ) {
  896. if ($base2) {
  897. $base = $base2;
  898. $rest = &$this->config->getREST('1.3', $this->_options);
  899. } else {
  900. $rest = &$this->config->getREST('1.0', $this->_options);
  901. }
  902. $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
  903. $state, $version, $chan->getName());
  904. if (PEAR::isError($url)) {
  905. return $url;
  906. }
  907. if ($parr['channel'] != $curchannel) {
  908. $this->configSet('default_channel', $curchannel);
  909. }
  910. if (!is_array($url)) {
  911. return $url;
  912. }
  913. $url['raw'] = false; // no checking is necessary for REST
  914. if (!is_array($url['info'])) {
  915. return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  916. 'this should never happen');
  917. }
  918. if (isset($url['info']['required'])) {
  919. if (!class_exists('PEAR_PackageFile_v2')) {
  920. require_once 'PEAR/PackageFile/v2.php';
  921. }
  922. $pf = new PEAR_PackageFile_v2;
  923. $pf->setRawChannel($remotechannel);
  924. } else {
  925. if (!class_exists('PEAR_PackageFile_v1')) {
  926. require_once 'PEAR/PackageFile/v1.php';
  927. }
  928. $pf = new PEAR_PackageFile_v1;
  929. }
  930. $pf->setRawPackage($url['package']);
  931. $pf->setDeps($url['info']);
  932. if ($url['compatible']) {
  933. $pf->setCompatible($url['compatible']);
  934. }
  935. $pf->setRawState($url['stability']);
  936. $url['info'] = &$pf;
  937. if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  938. $ext = '.tar';
  939. } else {
  940. $ext = '.tgz';
  941. }
  942. if (is_array($url) && isset($url['url'])) {
  943. $url['url'] .= $ext;
  944. }
  945. return $url;
  946. }
  947. return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
  948. }
  949. /**
  950. * @deprecated in favor of _getPackageDownloadUrl
  951. */
  952. function getPackageDownloadUrl($package, $version = null, $channel = false)
  953. {
  954. if ($version) {
  955. $package .= "-$version";
  956. }
  957. if ($this === null || $this->_registry === null) {
  958. $package = "http://pear.php.net/get/$package";
  959. } else {
  960. $chan = $this->_registry->getChannel($channel);
  961. if (PEAR::isError($chan)) {
  962. return '';
  963. }
  964. $package = "http://" . $chan->getServer() . "/get/$package";
  965. }
  966. if (!extension_loaded("zlib")) {
  967. $package .= '?uncompress=yes';
  968. }
  969. return $package;
  970. }
  971. /**
  972. * Retrieve a list of downloaded packages after a call to {@link download()}.
  973. *
  974. * Also resets the list of downloaded packages.
  975. * @return array
  976. */
  977. function getDownloadedPackages()
  978. {
  979. $ret = $this->_downloadedPackages;
  980. $this->_downloadedPackages = array();
  981. $this->_toDownload = array();
  982. return $ret;
  983. }
  984. function _downloadCallback($msg, $params = null)
  985. {
  986. switch ($msg) {
  987. case 'saveas':
  988. $this->log(1, "downloading $params ...");
  989. break;
  990. case 'done':
  991. $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  992. break;
  993. case 'bytesread':
  994. static $bytes;
  995. if (empty($bytes)) {
  996. $bytes = 0;
  997. }
  998. if (!($bytes % 10240)) {
  999. $this->log(1, '.', false);
  1000. }
  1001. $bytes += $params;
  1002. break;
  1003. case 'start':
  1004. if($params[1] == -1) {
  1005. $length = "Unknown size";
  1006. } else {
  1007. $length = number_format($params[1], 0, '', ',')." bytes";
  1008. }
  1009. $this->log(1, "Starting to download {$params[0]} ($length)");
  1010. break;
  1011. }
  1012. if (method_exists($this->ui, '_downloadCallback'))
  1013. $this->ui->_downloadCallback($msg, $params);
  1014. }
  1015. function _prependPath($path, $prepend)
  1016. {
  1017. if (strlen($prepend) > 0) {
  1018. if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  1019. if (preg_match('/^[a-z]:/i', $prepend)) {
  1020. $prepend = substr($prepend, 2);
  1021. } elseif ($prepend{0} != '\\') {
  1022. $prepend = "\\$prepend";
  1023. }
  1024. $path = substr($path, 0, 2) . $prepend . substr($path, 2);
  1025. } else {
  1026. $path = $prepend . $path;
  1027. }
  1028. }
  1029. return $path;
  1030. }
  1031. /**
  1032. * @param string
  1033. * @param integer
  1034. */
  1035. function pushError($errmsg, $code = -1)
  1036. {
  1037. array_push($this->_errorStack, array($errmsg, $code));
  1038. }
  1039. function getErrorMsgs()
  1040. {
  1041. $msgs = array();
  1042. $errs = $this->_errorStack;
  1043. foreach ($errs as $err) {
  1044. $msgs[] = $err[0];
  1045. }
  1046. $this->_errorStack = array();
  1047. return $msgs;
  1048. }
  1049. /**
  1050. * for BC
  1051. *
  1052. * @deprecated
  1053. */
  1054. function sortPkgDeps(&$packages, $uninstall = false)
  1055. {
  1056. $uninstall ?
  1057. $this->sortPackagesForUninstall($packages) :
  1058. $this->sortPackagesForInstall($packages);
  1059. }
  1060. /**
  1061. * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1062. *
  1063. * This uses the topological sort method from graph theory, and the
  1064. * Structures_Graph package to properly sort dependencies for installation.
  1065. * @param array an array of downloaded PEAR_Downloader_Packages
  1066. * @return array array of array(packagefilename, package.xml contents)
  1067. */
  1068. function sortPackagesForInstall(&$packages)
  1069. {
  1070. require_once 'Structures/Graph.php';
  1071. require_once 'Structures/Graph/Node.php';
  1072. require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
  1073. $depgraph = new Structures_Graph(true);
  1074. $nodes = array();
  1075. $reg = &$this->config->getRegistry();
  1076. foreach ($packages as $i => $package) {
  1077. $pname = $reg->parsedPackageNameToString(
  1078. array(
  1079. 'channel' => $package->getChannel(),
  1080. 'package' => strtolower($package->getPackage()),
  1081. ));
  1082. $nodes[$pname] = new Structures_Graph_Node;
  1083. $nodes[$pname]->setData($packages[$i]);
  1084. $depgraph->addNode($nodes[$pname]);
  1085. }
  1086. $deplinks = array();
  1087. foreach ($nodes as $package => $node) {
  1088. $pf = &$node->getData();
  1089. $pdeps = $pf->getDeps(true);
  1090. if (!$pdeps) {
  1091. continue;
  1092. }
  1093. if ($pf->getPackagexmlVersion() == '1.0') {
  1094. foreach ($pdeps as $dep) {
  1095. if ($dep['type'] != 'pkg' ||
  1096. (isset($dep['optional']) && $dep['optional'] == 'yes')) {
  1097. continue;
  1098. }
  1099. $dname = $reg->parsedPackageNameToString(
  1100. array(
  1101. 'channel' => 'pear.php.net',
  1102. 'package' => strtolower($dep['name']),
  1103. ));
  1104. if (isset($nodes[$dname])) {
  1105. if (!isset($deplinks[$dname])) {
  1106. $deplinks[$dname] = array();
  1107. }
  1108. $deplinks[$dname][$package] = 1;
  1109. // dependency is in installed packages
  1110. continue;
  1111. }
  1112. $dname = $reg->parsedPackageNameToString(
  1113. array(
  1114. 'channel' => 'pecl.php.net',
  1115. 'package' => strtolower($dep['name']),
  1116. ));
  1117. if (isset($nodes[$dname])) {
  1118. if (!isset($deplinks[$dname])) {
  1119. $deplinks[$dname] = array();
  1120. }
  1121. $deplinks[$dname][$package] = 1;
  1122. // dependency is in installed packages
  1123. continue;
  1124. }
  1125. }
  1126. } else {
  1127. // the only ordering we care about is:
  1128. // 1) subpackages must be installed before packages that depend on them
  1129. // 2) required deps must be installed before packages that depend on them
  1130. if (isset($pdeps['required']['subpackage'])) {
  1131. $t = $pdeps['required']['subpackage'];
  1132. if (!isset($t[0])) {
  1133. $t = array($t);
  1134. }
  1135. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1136. }
  1137. if (isset($pdeps['group'])) {
  1138. if (!isset($pdeps['group'][0])) {
  1139. $pdeps['group'] = array($pdeps['group']);
  1140. }
  1141. foreach ($pdeps['group'] as $group) {
  1142. if (isset($group['subpackage'])) {
  1143. $t = $group['subpackage'];
  1144. if (!isset($t[0])) {
  1145. $t = array($t);
  1146. }
  1147. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1148. }
  1149. }
  1150. }
  1151. if (isset($pdeps['optional']['subpackage'])) {
  1152. $t = $pdeps['optional']['subpackage'];
  1153. if (!isset($t[0])) {
  1154. $t = array($t);
  1155. }
  1156. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1157. }
  1158. if (isset($pdeps['required']['package'])) {
  1159. $t = $pdeps['required']['package'];
  1160. if (!isset($t[0])) {
  1161. $t = array($t);
  1162. }
  1163. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1164. }
  1165. if (isset($pdeps['group'])) {
  1166. if (!isset($pdeps['group'][0])) {
  1167. $pdeps['group'] = array($pdeps['group']);
  1168. }
  1169. foreach ($pdeps['group'] as $group) {
  1170. if (isset($group['package'])) {
  1171. $t = $group['package'];
  1172. if (!isset($t[0])) {
  1173. $t = array($t);
  1174. }
  1175. $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1176. }
  1177. }
  1178. }
  1179. }
  1180. }
  1181. $this->_detectDepCycle($deplinks);
  1182. foreach ($deplinks as $dependent => $parents) {
  1183. foreach ($parents as $parent => $unused) {
  1184. $nodes[$dependent]->connectTo($nodes[$parent]);
  1185. }
  1186. }
  1187. $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
  1188. $ret = array();
  1189. for ($i = 0, $count = count($installOrder); $i < $count; $i++) {
  1190. foreach ($installOrder[$i] as $index => $sortedpackage) {
  1191. $data = &$installOrder[$i][$index]->getData();
  1192. $ret[] = &$nodes[$reg->parsedPackageNameToString(
  1193. array(
  1194. 'channel' => $data->getChannel(),
  1195. 'package' => strtolower($data->getPackage()),
  1196. ))]->getData();
  1197. }
  1198. }
  1199. $packages = $ret;
  1200. return;
  1201. }
  1202. /**
  1203. * Detect recursive links between dependencies and break the cycles
  1204. *
  1205. * @param array
  1206. * @access private
  1207. */
  1208. function _detectDepCycle(&$deplinks)
  1209. {
  1210. do {
  1211. $keepgoing = false;
  1212. foreach ($deplinks as $dep => $parents) {
  1213. foreach ($parents as $parent => $unused) {
  1214. // reset the parent cycle detector
  1215. $this->_testCycle(null, null, null);
  1216. if ($this->_testCycle($dep, $deplinks, $parent)) {
  1217. $keepgoing = true;
  1218. unset($deplinks[$dep][$parent]);
  1219. if (count($deplinks[$dep]) == 0) {
  1220. unset($deplinks[$dep]);
  1221. }
  1222. continue 3;
  1223. }
  1224. }
  1225. }
  1226. } while ($keepgoing);
  1227. }
  1228. function _testCycle($test, $deplinks, $dep)
  1229. {
  1230. static $visited = array();
  1231. if ($test === null) {
  1232. $visited = array();
  1233. return;
  1234. }
  1235. // this happens when a parent has a dep cycle on another dependency
  1236. // but the child is not part of the cycle
  1237. if (isset($visited[$dep])) {
  1238. return false;
  1239. }
  1240. $visited[$dep] = 1;
  1241. if ($test == $dep) {
  1242. return true;
  1243. }
  1244. if (isset($deplinks[$dep])) {
  1245. if (in_array($test, array_keys($deplinks[$dep]), true)) {
  1246. return true;
  1247. }
  1248. foreach ($deplinks[$dep] as $parent => $unused) {
  1249. if ($this->_testCycle($test, $deplinks, $parent)) {
  1250. return true;
  1251. }
  1252. }
  1253. }
  1254. return false;
  1255. }
  1256. /**
  1257. * Set up the dependency for installation parsing
  1258. *
  1259. * @param array $t dependency information
  1260. * @param PEAR_Registry $reg
  1261. * @param array $deplinks list of dependency links already established
  1262. * @param array $nodes all existing package nodes
  1263. * @param string $package parent package name
  1264. * @access private
  1265. */
  1266. function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
  1267. {
  1268. foreach ($t as $dep) {
  1269. $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel'];
  1270. $dname = $reg->parsedPackageNameToString(
  1271. array(
  1272. 'channel' => $depchannel,
  1273. 'package' => strtolower($dep['name']),
  1274. ));
  1275. if (isset($nodes[$dname])) {
  1276. if (!isset($deplinks[$dname])) {
  1277. $deplinks[$dname] = array();
  1278. }
  1279. $deplinks[$dname][$package] = 1;
  1280. }
  1281. }
  1282. }
  1283. function _dependsOn($a, $b)
  1284. {
  1285. return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b);
  1286. }
  1287. function _checkDepTree($channel, $package, $b, $checked = array())
  1288. {
  1289. $checked[$channel][$package] = true;
  1290. if (!isset($this->_depTree[$channel][$package])) {
  1291. return false;
  1292. }
  1293. if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
  1294. [strtolower($b->getPackage())])) {
  1295. return true;
  1296. }
  1297. foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
  1298. foreach ($packages as $pa => $true) {
  1299. if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
  1300. return true;
  1301. }
  1302. }
  1303. }
  1304. return false;
  1305. }
  1306. function _sortInstall($a, $b)
  1307. {
  1308. if (!$a->getDeps() && !$b->getDeps()) {
  1309. return 0; // neither package has dependencies, order is insignificant
  1310. }
  1311. if ($a->getDeps() && !$b->getDeps()) {
  1312. return 1; // $a must be installed after $b because $a has dependencies
  1313. }
  1314. if (!$a->getDeps() && $b->getDeps()) {
  1315. return -1; // $b must be installed after $a because $b has dependencies
  1316. }
  1317. // both packages have dependencies
  1318. if ($this->_dependsOn($a, $b)) {
  1319. return 1;
  1320. }
  1321. if ($this->_dependsOn($b, $a)) {
  1322. return -1;
  1323. }
  1324. return 0;
  1325. }
  1326. /**
  1327. * Download a file through HTTP. Considers suggested file name in
  1328. * Content-disposition: header and can run a callback function for
  1329. * different events. The callback will be called with two
  1330. * parameters: the callback type, and parameters. The implemented
  1331. * callback types are:
  1332. *
  1333. * 'setup' called at the very beginning, parameter is a UI object
  1334. * that should be used for all output
  1335. * 'message' the parameter is a string with an informational message
  1336. * 'saveas' may be used to save with a different file name, the
  1337. * parameter is the filename that is about to be used.
  1338. * If a 'saveas' callback returns a non-empty string,
  1339. * that file name will be used as the filename instead.
  1340. * Note that $save_dir will not be affected by this, only
  1341. * the basename of the file.
  1342. * 'start' download is starting, parameter is number of bytes
  1343. * that are expected, or -1 if unknown
  1344. * 'bytesread' parameter is the number of bytes read so far
  1345. * 'done' download is complete, parameter is the total number
  1346. * of bytes read
  1347. * 'connfailed' if the TCP/SSL connection fails, this callback is called
  1348. * with array(host,port,errno,errmsg)
  1349. * 'writefailed' if writing to disk fails, this callback is called
  1350. * with array(destfile,errmsg)
  1351. *
  1352. * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1353. * setting), the proxy will be used.
  1354. *
  1355. * @param string $url the URL to download
  1356. * @param object $ui PEAR_Frontend_* instance
  1357. * @param object $config PEAR_Config instance
  1358. * @param string $save_dir directory to save file in
  1359. * @param mixed $callback function/method to call for status
  1360. * updates
  1361. * @param false|string|array $lastmodified header values to check against for caching
  1362. * use false to return the header values from this download
  1363. * @param false|array $accept Accept headers to send
  1364. * @param false|string $channel Channel to use for retrieving authentication
  1365. * @return string|array Returns the full path of the downloaded file or a PEAR
  1366. * error on failure. If the error is caused by
  1367. * socket-related errors, the error object will
  1368. * have the fsockopen error code available through
  1369. * getCode(). If caching is requested, then return the header
  1370. * values.
  1371. *
  1372. * @access public
  1373. */
  1374. function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
  1375. $accept = false, $channel = false)
  1376. {
  1377. static $redirect = 0;
  1378. // always reset , so we are clean case of error
  1379. $wasredirect = $redirect;
  1380. $redirect = 0;
  1381. if ($callback) {
  1382. call_user_func($callback, 'setup', array(&$ui));
  1383. }
  1384. $info = parse_url($url);
  1385. if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
  1386. return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
  1387. }
  1388. if (!isset($info['host'])) {
  1389. return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
  1390. }
  1391. $host = isset($info['host']) ? $info['host'] : null;
  1392. $port = isset($info['port']) ? $info['port'] : null;
  1393. $path = isset($info['path']) ? $info['path'] : null;
  1394. if (isset($this)) {
  1395. $config = &$this->config;
  1396. } else {
  1397. $config = &PEAR_Config::singleton();
  1398. }
  1399. $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1400. if ($config->get('http_proxy') &&
  1401. $proxy = parse_url($config->get('http_proxy'))) {
  1402. $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
  1403. if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
  1404. $proxy_host = 'ssl://' . $proxy_host;
  1405. }
  1406. $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
  1407. $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
  1408. $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
  1409. if ($callback) {
  1410. call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1411. }
  1412. }
  1413. if (empty($port)) {
  1414. $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
  1415. }
  1416. $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
  1417. if ($proxy_host != '') {
  1418. $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1419. if (!$fp) {
  1420. if ($callback) {
  1421. call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1422. $errno, $errstr));
  1423. }
  1424. return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1425. }
  1426. if ($lastmodified === false || $lastmodified) {
  1427. $request = "GET $url HTTP/1.1\r\n";
  1428. $request .= "Host: $host\r\n";
  1429. } else {
  1430. $request = "GET $url HTTP/1.0\r\n";
  1431. $request .= "Host: $host\r\n";
  1432. }
  1433. } else {
  1434. $network_host = $host;
  1435. if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1436. $network_host = 'ssl://' . $host;
  1437. }
  1438. $fp = @fsockopen($network_host, $port, $errno, $errstr);
  1439. if (!$fp) {
  1440. if ($callback) {
  1441. call_user_func($callback, 'connfailed', array($host, $port,
  1442. $errno, $errstr));
  1443. }
  1444. return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1445. }
  1446. if ($lastmodified === false || $lastmodified) {
  1447. $request = "GET $path HTTP/1.1\r\n";
  1448. $request .= "Host: $host\r\n";
  1449. } else {
  1450. $request = "GET $path HTTP/1.0\r\n";
  1451. $request .= "Host: $host\r\n";
  1452. }
  1453. }
  1454. $ifmodifiedsince = '';
  1455. if (is_array($lastmodified)) {
  1456. if (isset($lastmodified['Last-Modified'])) {
  1457. $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
  1458. }
  1459. if (isset($lastmodified['ETag'])) {
  1460. $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
  1461. }
  1462. } else {
  1463. $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
  1464. }
  1465. $request .= $ifmodifiedsince .
  1466. "User-Agent: PEAR/1.9.4/PHP/" . PHP_VERSION . "\r\n";
  1467. if (isset($this)) { // only pass in authentication for non-static calls
  1468. $username = $config->get('username', null, $channel);
  1469. $password = $config->get('password', null, $channel);
  1470. if ($username && $password) {
  1471. $tmp = base64_encode("$username:$password");
  1472. $request .= "Authorization: Basic $tmp\r\n";
  1473. }
  1474. }
  1475. if ($proxy_host != '' && $proxy_user != '') {
  1476. $request .= 'Proxy-Authorization: Basic ' .
  1477. base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1478. }
  1479. if ($accept) {
  1480. $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
  1481. }
  1482. $request .= "Connection: close\r\n";
  1483. $request .= "\r\n";
  1484. fwrite($fp, $request);
  1485. $headers = array();
  1486. $reply = 0;
  1487. while (trim($line = fgets($fp, 1024))) {
  1488. if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
  1489. $headers[strtolower($matches[1])] = trim($matches[2]);
  1490. } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1491. $reply = (int)$matches[1];
  1492. if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
  1493. return false;
  1494. }
  1495. if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
  1496. return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)");
  1497. }
  1498. }
  1499. }
  1500. if ($reply != 200) {
  1501. if (!isset($headers['location'])) {
  1502. return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)");
  1503. }
  1504. if ($wasredirect > 4) {
  1505. return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)");
  1506. }
  1507. $redirect = $wasredirect + 1;
  1508. return $this->downloadHttp($headers['location'],
  1509. $ui, $save_dir, $callback, $lastmodified, $accept);
  1510. }
  1511. if (isset($headers['content-disposition']) &&
  1512. preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
  1513. $save_as = basename($matches[1]);
  1514. } else {
  1515. $save_as = basename($url);
  1516. }
  1517. if ($callback) {
  1518. $tmp = call_user_func($callback, 'saveas', $save_as);
  1519. if ($tmp) {
  1520. $save_as = $tmp;
  1521. }
  1522. }
  1523. $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1524. if (is_link($dest_file)) {
  1525. return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack');
  1526. }
  1527. if (!$wp = @fopen($dest_file, 'wb')) {
  1528. fclose($fp);
  1529. if ($callback) {
  1530. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1531. }
  1532. return PEAR::raiseError("could not open $dest_file for writing");
  1533. }
  1534. $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
  1535. $bytes = 0;
  1536. if ($callback) {
  1537. call_user_func($callback, 'start', array(basename($dest_file), $length));
  1538. }
  1539. while ($data = fread($fp, 1024)) {
  1540. $bytes += strlen($data);
  1541. if ($callback) {
  1542. call_user_func($callback, 'bytesread', $bytes);
  1543. }
  1544. if (!@fwrite($wp, $data)) {
  1545. fclose($fp);
  1546. if ($callback) {
  1547. call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1548. }
  1549. return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1550. }
  1551. }
  1552. fclose($fp);
  1553. fclose($wp);
  1554. if ($callback) {
  1555. call_user_func($callback, 'done', $bytes);
  1556. }
  1557. if ($lastmodified === false || $lastmodified) {
  1558. if (isset($headers['etag'])) {
  1559. $lastmodified = array('ETag' => $headers['etag']);
  1560. }
  1561. if (isset($headers['last-modified'])) {
  1562. if (is_array($lastmodified)) {
  1563. $lastmodified['Last-Modified'] = $headers['last-modified'];
  1564. } else {
  1565. $lastmodified = $headers['last-modified'];
  1566. }
  1567. }
  1568. return array($dest_file, $lastmodified, $headers);
  1569. }
  1570. return $dest_file;
  1571. }
  1572. }