home *** CD-ROM | disk | FTP | other *** search
/ Cricao de Sites - 650 Layouts Prontos / WebMasters.iso / Servidores / xampp-win32-1.6.7-installer.exe / php / tmp / PEAR-1.7.1 / PEAR / Downloader.php < prev    next >
Encoding:
PHP Script  |  2008-02-15  |  68.5 KB  |  1,753 lines

  1. <?php
  2. /**
  3.  * PEAR_Downloader, the PEAR Installer's download utility class
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   pear
  14.  * @package    PEAR
  15.  * @author     Greg Beaver <cellog@php.net>
  16.  * @author     Stig Bakken <ssb@php.net>
  17.  * @author     Tomas V. V. Cox <cox@idecnet.com>
  18.  * @author     Martin Jansen <mj@php.net>
  19.  * @copyright  1997-2008 The PHP Group
  20.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  21.  * @version    CVS: $Id: Downloader.php,v 1.137 2008/01/29 03:21:01 cellog Exp $
  22.  * @link       http://pear.php.net/package/PEAR
  23.  * @since      File available since Release 1.3.0
  24.  */
  25.  
  26. /**
  27.  * Needed for constants, extending
  28.  */
  29. require_once 'PEAR/Common.php';
  30.  
  31. define('PEAR_INSTALLER_OK',       1);
  32. define('PEAR_INSTALLER_FAILED',   0);
  33. define('PEAR_INSTALLER_SKIPPED', -1);
  34. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  35.  
  36. /**
  37.  * Administration class used to download anything from the internet (PEAR Packages,
  38.  * static URLs, xml files)
  39.  *
  40.  * @category   pear
  41.  * @package    PEAR
  42.  * @author     Greg Beaver <cellog@php.net>
  43.  * @author     Stig Bakken <ssb@php.net>
  44.  * @author     Tomas V. V. Cox <cox@idecnet.com>
  45.  * @author     Martin Jansen <mj@php.net>
  46.  * @copyright  1997-2008 The PHP Group
  47.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  48.  * @version    Release: 1.7.1
  49.  * @link       http://pear.php.net/package/PEAR
  50.  * @since      Class available since Release 1.3.0
  51.  */
  52. class PEAR_Downloader extends PEAR_Common
  53. {
  54.     /**
  55.      * @var PEAR_Registry
  56.      * @access private
  57.      */
  58.     var $_registry;
  59.  
  60.     /**
  61.      * @var PEAR_Remote
  62.      * @access private
  63.      */
  64.     var $_remote;
  65.  
  66.     /**
  67.      * Preferred Installation State (snapshot, devel, alpha, beta, stable)
  68.      * @var string|null
  69.      * @access private
  70.      */
  71.     var $_preferredState;
  72.  
  73.     /**
  74.      * Options from command-line passed to Install.
  75.      *
  76.      * Recognized options:<br />
  77.      *  - onlyreqdeps   : install all required dependencies as well
  78.      *  - alldeps       : install all dependencies, including optional
  79.      *  - installroot   : base relative path to install files in
  80.      *  - force         : force a download even if warnings would prevent it
  81.      *  - nocompress    : download uncompressed tarballs
  82.      * @see PEAR_Command_Install
  83.      * @access private
  84.      * @var array
  85.      */
  86.     var $_options;
  87.  
  88.     /**
  89.      * Downloaded Packages after a call to download().
  90.      *
  91.      * Format of each entry:
  92.      *
  93.      * <code>
  94.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  95.      *    'info' => array() // parsed package.xml
  96.      * );
  97.      * </code>
  98.      * @access private
  99.      * @var array
  100.      */
  101.     var $_downloadedPackages = array();
  102.  
  103.     /**
  104.      * Packages slated for download.
  105.      *
  106.      * This is used to prevent downloading a package more than once should it be a dependency
  107.      * for two packages to be installed.
  108.      * Format of each entry:
  109.      *
  110.      * <pre>
  111.      * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
  112.      * );
  113.      * </pre>
  114.      * @access private
  115.      * @var array
  116.      */
  117.     var $_toDownload = array();
  118.  
  119.     /**
  120.      * Array of every package installed, with names lower-cased.
  121.      *
  122.      * Format:
  123.      * <code>
  124.      * array('package1' => 0, 'package2' => 1, );
  125.      * </code>
  126.      * @var array
  127.      */
  128.     var $_installed = array();
  129.  
  130.     /**
  131.      * @var array
  132.      * @access private
  133.      */
  134.     var $_errorStack = array();
  135.     
  136.     /**
  137.      * @var boolean
  138.      * @access private
  139.      */
  140.     var $_internalDownload = false;
  141.  
  142.     /**
  143.      * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
  144.      * @var array
  145.      * @access private
  146.      */
  147.     var $_packageSortTree;
  148.  
  149.     /**
  150.      * Temporary directory, or configuration value where downloads will occur
  151.      * @var string
  152.      */
  153.     var $_downloadDir;
  154.     // {{{ PEAR_Downloader()
  155.  
  156.     /**
  157.      * @param PEAR_Frontend_*
  158.      * @param array
  159.      * @param PEAR_Config
  160.      */
  161.     function PEAR_Downloader(&$ui, $options, &$config)
  162.     {
  163.         parent::PEAR_Common();
  164.         $this->_options = $options;
  165.         $this->config = &$config;
  166.         $this->_preferredState = $this->config->get('preferred_state');
  167.         $this->ui = &$ui;
  168.         if (!$this->_preferredState) {
  169.             // don't inadvertantly use a non-set preferred_state
  170.             $this->_preferredState = null;
  171.         }
  172.  
  173.         if (isset($this->_options['installroot'])) {
  174.             $this->config->setInstallRoot($this->_options['installroot']);
  175.         }
  176.         $this->_registry = &$config->getRegistry();
  177.         $this->_remote = &$config->getRemote();
  178.  
  179.         if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
  180.             $this->_installed = $this->_registry->listAllPackages();
  181.             foreach ($this->_installed as $key => $unused) {
  182.                 if (!count($unused)) {
  183.                     continue;
  184.                 }
  185.                 $strtolower = create_function('$a','return strtolower($a);');
  186.                 array_walk($this->_installed[$key], $strtolower);
  187.             }
  188.         }
  189.     }
  190.  
  191.     /**
  192.      * Attempt to discover a channel's remote capabilities from
  193.      * its server name
  194.      * @param string
  195.      * @return boolean
  196.      */
  197.     function discover($channel)
  198.     {
  199.         $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
  200.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  201.         $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
  202.         if (!class_exists('System')) {
  203.             require_once 'System.php';
  204.         }
  205.         $a = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui,
  206.             System::mktemp(array('-d')), $callback, false);
  207.         PEAR::popErrorHandling();
  208.         if (PEAR::isError($a)) {
  209.             return false;
  210.         }
  211.         list($a, $lastmodified) = $a;
  212.         if (!class_exists('PEAR_ChannelFile')) {
  213.             require_once 'PEAR/ChannelFile.php';
  214.         }
  215.         $b = new PEAR_ChannelFile;
  216.         if ($b->fromXmlFile($a)) {
  217.             unlink($a);
  218.             if ($this->config->get('auto_discover')) {
  219.                 $this->_registry->addChannel($b, $lastmodified);
  220.                 $alias = $b->getName();
  221.                 if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
  222.                     $alias = $b->getAlias();
  223.                 }
  224.                 $this->log(1, 'Auto-discovered channel "' . $channel .
  225.                     '", alias "' . $alias . '", adding to registry');
  226.             }
  227.             return true;
  228.         }
  229.         unlink($a);
  230.         return false;
  231.     }
  232.  
  233.     /**
  234.      * For simpler unit-testing
  235.      * @param PEAR_Downloader
  236.      * @return PEAR_Downloader_Package
  237.      */
  238.     function &newDownloaderPackage(&$t)
  239.     {
  240.         if (!class_exists('PEAR_Downloader_Package')) {
  241.             require_once 'PEAR/Downloader/Package.php';
  242.         }
  243.         $a = &new PEAR_Downloader_Package($t);
  244.         return $a;
  245.     }
  246.  
  247.     /**
  248.      * For simpler unit-testing
  249.      * @param PEAR_Config
  250.      * @param array
  251.      * @param array
  252.      * @param int
  253.      */
  254.     function &getDependency2Object(&$c, $i, $p, $s)
  255.     {
  256.         if (!class_exists('PEAR_Dependency2')) {
  257.             require_once 'PEAR/Dependency2.php';
  258.         }
  259.         $z = &new PEAR_Dependency2($c, $i, $p, $s);
  260.         return $z;
  261.     }
  262.  
  263.     function &download($params)
  264.     {
  265.         if (!count($params)) {
  266.             $a = array();
  267.             return $a;
  268.         }
  269.         if (!isset($this->_registry)) {
  270.             $this->_registry = &$this->config->getRegistry();
  271.         }
  272.         if (!isset($this->_remote)) {
  273.             $this->_remote = &$this->config->getRemote();
  274.         }
  275.         $channelschecked = array();
  276.         // convert all parameters into PEAR_Downloader_Package objects
  277.         foreach ($params as $i => $param) {
  278.             $params[$i] = &$this->newDownloaderPackage($this);
  279.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  280.             $err = $params[$i]->initialize($param);
  281.             PEAR::staticPopErrorHandling();
  282.             if (!$err) {
  283.                 // skip parameters that were missed by preferred_state
  284.                 continue;
  285.             }
  286.             if (PEAR::isError($err)) {
  287.                 if (!isset($this->_options['soft'])) {
  288.                     $this->log(0, $err->getMessage());
  289.                 }
  290.                 $params[$i] = false;
  291.                 if (is_object($param)) {
  292.                     $param = $param->getChannel() . '/' . $param->getPackage();
  293.                 }
  294.                 $this->pushError('Package "' . $param . '" is not valid',
  295.                     PEAR_INSTALLER_SKIPPED);
  296.             } else {
  297.                 do {
  298.                     if ($params[$i] && $params[$i]->getType() == 'local') {
  299.                         // bug #7090
  300.                         // skip channel.xml check for local packages
  301.                         break;
  302.                     }
  303.                     if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
  304.                           !isset($this->_options['offline'])) {
  305.                         $channelschecked[$params[$i]->getChannel()] = true;
  306.                         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  307.                         if (!class_exists('System')) {
  308.                             require_once 'System.php';
  309.                         }
  310.                         $curchannel = &$this->_registry->getChannel($params[$i]->getChannel());
  311.                         if (PEAR::isError($curchannel)) {
  312.                             PEAR::staticPopErrorHandling();
  313.                             return $this->raiseError($curchannel);
  314.                         }
  315.                         if (PEAR::isError($dir = $this->getDownloadDir())) {
  316.                             PEAR::staticPopErrorHandling();
  317.                             break;
  318.                         }
  319.                         $mirror = $this->config->get('preferred_mirror', null,
  320.                                                      $params[$i]->getChannel());
  321.                         $a = $this->downloadHttp('http://' . $mirror .
  322.                             '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
  323.  
  324.                         PEAR::staticPopErrorHandling();
  325.                         if (PEAR::isError($a) || !$a) {
  326.                             break;
  327.                         }
  328.                         $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
  329.                             'updated its protocols, use "channel-update ' . $params[$i]->getChannel() .
  330.                             '" to update');
  331.                     }
  332.                 } while (false);
  333.                 if ($params[$i] && !isset($this->_options['downloadonly'])) {
  334.                     if (isset($this->_options['packagingroot'])) {
  335.                         $checkdir = $this->_prependPath(
  336.                             $this->config->get('php_dir', null, $params[$i]->getChannel()),
  337.                             $this->_options['packagingroot']);
  338.                     } else {
  339.                         $checkdir = $this->config->get('php_dir',
  340.                             null, $params[$i]->getChannel());
  341.                     }
  342.                     while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
  343.                         $checkdir = dirname($checkdir);
  344.                     }
  345.                     if ($checkdir == '.') {
  346.                         $checkdir = '/';
  347.                     }
  348.                     if (!is_writeable($checkdir)) {
  349.                         return PEAR::raiseError('Cannot install, php_dir for channel "' .
  350.                             $params[$i]->getChannel() . '" is not writeable by the current user');
  351.                     }
  352.                 }
  353.             }
  354.         }
  355.         unset($channelschecked);
  356.         PEAR_Downloader_Package::removeDuplicates($params);
  357.         if (!count($params)) {
  358.             $a = array();
  359.             return $a;
  360.         }
  361.         if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
  362.             $reverify = true;
  363.             while ($reverify) {
  364.                 $reverify = false;
  365.                 foreach ($params as $i => $param) {
  366.                     //PHP Bug 40768 / PEAR Bug #10944
  367.                     //Nested foreaches fail in PHP 5.2.1
  368.                     key($params);
  369.                     $ret = $params[$i]->detectDependencies($params);
  370.                     if (PEAR::isError($ret)) {
  371.                         $reverify = true;
  372.                         $params[$i] = false;
  373.                         PEAR_Downloader_Package::removeDuplicates($params);
  374.                         if (!isset($this->_options['soft'])) {
  375.                             $this->log(0, $ret->getMessage());
  376.                         }
  377.                         continue 2;
  378.                     }
  379.                 }
  380.             }
  381.         }
  382.         if (isset($this->_options['offline'])) {
  383.             $this->log(3, 'Skipping dependency download check, --offline specified');
  384.         }
  385.         if (!count($params)) {
  386.             $a = array();
  387.             return $a;
  388.         }
  389.         while (PEAR_Downloader_Package::mergeDependencies($params));
  390.         PEAR_Downloader_Package::removeDuplicates($params, true);
  391.         $errorparams = array();
  392.         if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
  393.             if (count($errorparams)) {
  394.                 foreach ($errorparams as $param) {
  395.                     $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
  396.                     $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
  397.                 }
  398.                 $a = array();
  399.                 return $a;
  400.             }
  401.         }
  402.         PEAR_Downloader_Package::removeInstalled($params);
  403.         if (!count($params)) {
  404.             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  405.             $a = array();
  406.             return $a;
  407.         }
  408.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  409.         $err = $this->analyzeDependencies($params);
  410.         PEAR::popErrorHandling();
  411.         if (!count($params)) {
  412.             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
  413.             $a = array();
  414.             return $a;
  415.         }
  416.         $ret = array();
  417.         $newparams = array();
  418.         if (isset($this->_options['pretend'])) {
  419.             return $params;
  420.         }
  421.         $somefailed = false;
  422.         foreach ($params as $i => $package) {
  423.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  424.             $pf = &$params[$i]->download();
  425.             PEAR::staticPopErrorHandling();
  426.             if (PEAR::isError($pf)) {
  427.                 if (!isset($this->_options['soft'])) {
  428.                     $this->log(1, $pf->getMessage());
  429.                     $this->log(0, 'Error: cannot download "' .
  430.                         $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
  431.                             true) .
  432.                         '"');
  433.                 }
  434.                 $somefailed = true;
  435.                 continue;
  436.             }
  437.             $newparams[] = &$params[$i];
  438.             $ret[] = array('file' => $pf->getArchiveFile(),
  439.                                    'info' => &$pf,
  440.                                    'pkg' => $pf->getPackage());
  441.         }
  442.         if ($somefailed) {
  443.             // remove params that did not download successfully
  444.             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  445.             $err = $this->analyzeDependencies($newparams, true);
  446.             PEAR::popErrorHandling();
  447.             if (!count($newparams)) {
  448.                 $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
  449.                 $a = array();
  450.                 return $a;
  451.             }
  452.         }
  453.         $this->_downloadedPackages = $ret;
  454.         return $newparams;
  455.     }
  456.  
  457.     /**
  458.      * @param array all packages to be installed
  459.      */
  460.     function analyzeDependencies(&$params, $force = false)
  461.     {
  462.         $hasfailed = $failed = false;
  463.         if (isset($this->_options['downloadonly'])) {
  464.             return;
  465.         }
  466.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  467.         $redo = true;
  468.         $reset = false;
  469.         while ($redo) {
  470.             $redo = false;
  471.             foreach ($params as $i => $param) {
  472.                 $deps = $param->getDeps();
  473.                 if (!$deps) {
  474.                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  475.                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  476.                     if ($param->getType() == 'xmlrpc') {
  477.                         $send = $param->getDownloadURL();
  478.                     } else {
  479.                         $send = $param->getPackageFile();
  480.                     }
  481.                     $installcheck = $depchecker->validatePackage($send, $this, $params);
  482.                     if (PEAR::isError($installcheck)) {
  483.                         if (!isset($this->_options['soft'])) {
  484.                             $this->log(0, $installcheck->getMessage());
  485.                         }
  486.                         $hasfailed = true;
  487.                         $params[$i] = false;
  488.                         $reset = true;
  489.                         $redo = true;
  490.                         $failed = false;
  491.                         PEAR_Downloader_Package::removeDuplicates($params);
  492.                         continue 2;
  493.                     }
  494.                     continue;
  495.                 }
  496.                 if (!$reset && $param->alreadyValidated() && !$force) {
  497.                     continue;
  498.                 }
  499.                 if (count($deps)) {
  500.                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
  501.                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
  502.                     if ($param->getType() == 'xmlrpc') {
  503.                         $send = $param->getDownloadURL();
  504.                     } else {
  505.                         $send = $param->getPackageFile();
  506.                     }
  507.                     $installcheck = $depchecker->validatePackage($send, $this, $params);
  508.                     if (PEAR::isError($installcheck)) {
  509.                         if (!isset($this->_options['soft'])) {
  510.                             $this->log(0, $installcheck->getMessage());
  511.                         }
  512.                         $hasfailed = true;
  513.                         $params[$i] = false;
  514.                         $reset = true;
  515.                         $redo = true;
  516.                         $failed = false;
  517.                         PEAR_Downloader_Package::removeDuplicates($params);
  518.                         continue 2;
  519.                     }
  520.                     $failed = false;
  521.                     if (isset($deps['required'])) {
  522.                         foreach ($deps['required'] as $type => $dep) {
  523.                             // note: Dependency2 will never return a PEAR_Error if ignore-errors
  524.                             // is specified, so soft is needed to turn off logging
  525.                             if (!isset($dep[0])) {
  526.                                 if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
  527.                                       true, $params))) {
  528.                                     $failed = true;
  529.                                     if (!isset($this->_options['soft'])) {
  530.                                         $this->log(0, $e->getMessage());
  531.                                     }
  532.                                 } elseif (is_array($e) && !$param->alreadyValidated()) {
  533.                                     if (!isset($this->_options['soft'])) {
  534.                                         $this->log(0, $e[0]);
  535.                                     }
  536.                                 }
  537.                             } else {
  538.                                 foreach ($dep as $d) {
  539.                                     if (PEAR::isError($e =
  540.                                           $depchecker->{"validate{$type}Dependency"}($d,
  541.                                           true, $params))) {
  542.                                         $failed = true;
  543.                                         if (!isset($this->_options['soft'])) {
  544.                                             $this->log(0, $e->getMessage());
  545.                                         }
  546.                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
  547.                                         if (!isset($this->_options['soft'])) {
  548.                                             $this->log(0, $e[0]);
  549.                                         }
  550.                                     }
  551.                                 }
  552.                             }
  553.                         }
  554.                         if (isset($deps['optional'])) {
  555.                             foreach ($deps['optional'] as $type => $dep) {
  556.                                 if (!isset($dep[0])) {
  557.                                     if (PEAR::isError($e =
  558.                                           $depchecker->{"validate{$type}Dependency"}($dep,
  559.                                           false, $params))) {
  560.                                         $failed = true;
  561.                                         if (!isset($this->_options['soft'])) {
  562.                                             $this->log(0, $e->getMessage());
  563.                                         }
  564.                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
  565.                                         if (!isset($this->_options['soft'])) {
  566.                                             $this->log(0, $e[0]);
  567.                                         }
  568.                                     }
  569.                                 } else {
  570.                                     foreach ($dep as $d) {
  571.                                         if (PEAR::isError($e =
  572.                                               $depchecker->{"validate{$type}Dependency"}($d,
  573.                                               false, $params))) {
  574.                                             $failed = true;
  575.                                             if (!isset($this->_options['soft'])) {
  576.                                                 $this->log(0, $e->getMessage());
  577.                                             }
  578.                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
  579.                                             if (!isset($this->_options['soft'])) {
  580.                                                 $this->log(0, $e[0]);
  581.                                             }
  582.                                         }
  583.                                     }
  584.                                 }
  585.                             }
  586.                         }
  587.                         $groupname = $param->getGroup();
  588.                         if (isset($deps['group']) && $groupname) {
  589.                             if (!isset($deps['group'][0])) {
  590.                                 $deps['group'] = array($deps['group']);
  591.                             }
  592.                             $found = false;
  593.                             foreach ($deps['group'] as $group) {
  594.                                 if ($group['attribs']['name'] == $groupname) {
  595.                                     $found = true;
  596.                                     break;
  597.                                 }
  598.                             }
  599.                             if ($found) {
  600.                                 unset($group['attribs']);
  601.                                 foreach ($group as $type => $dep) {
  602.                                     if (!isset($dep[0])) {
  603.                                         if (PEAR::isError($e =
  604.                                               $depchecker->{"validate{$type}Dependency"}($dep,
  605.                                               false, $params))) {
  606.                                             $failed = true;
  607.                                             if (!isset($this->_options['soft'])) {
  608.                                                 $this->log(0, $e->getMessage());
  609.                                             }
  610.                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
  611.                                             if (!isset($this->_options['soft'])) {
  612.                                                 $this->log(0, $e[0]);
  613.                                             }
  614.                                         }
  615.                                     } else {
  616.                                         foreach ($dep as $d) {
  617.                                             if (PEAR::isError($e =
  618.                                                   $depchecker->{"validate{$type}Dependency"}($d,
  619.                                                   false, $params))) {
  620.                                                 $failed = true;
  621.                                                 if (!isset($this->_options['soft'])) {
  622.                                                     $this->log(0, $e->getMessage());
  623.                                                 }
  624.                                             } elseif (is_array($e) && !$param->alreadyValidated()) {
  625.                                                 if (!isset($this->_options['soft'])) {
  626.                                                     $this->log(0, $e[0]);
  627.                                                 }
  628.                                             }
  629.                                         }
  630.                                     }
  631.                                 }
  632.                             }
  633.                         }
  634.                     } else {
  635.                         foreach ($deps as $dep) {
  636.                             if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
  637.                                 $failed = true;
  638.                                 if (!isset($this->_options['soft'])) {
  639.                                     $this->log(0, $e->getMessage());
  640.                                 }
  641.                             } elseif (is_array($e) && !$param->alreadyValidated()) {
  642.                                 if (!isset($this->_options['soft'])) {
  643.                                     $this->log(0, $e[0]);
  644.                                 }
  645.                             }
  646.                         }
  647.                     }
  648.                     $params[$i]->setValidated();
  649.                 }
  650.                 if ($failed) {
  651.                     $hasfailed = true;
  652.                     $params[$i] = false;
  653.                     $reset = true;
  654.                     $redo = true;
  655.                     $failed = false;
  656.                     PEAR_Downloader_Package::removeDuplicates($params);
  657.                     continue 2;
  658.                 }
  659.             }
  660.         }
  661.         PEAR::staticPopErrorHandling();
  662.         if ($hasfailed && (isset($this->_options['ignore-errors']) ||
  663.               isset($this->_options['nodeps']))) {
  664.             // this is probably not needed, but just in case
  665.             if (!isset($this->_options['soft'])) {
  666.                 $this->log(0, 'WARNING: dependencies failed');
  667.             }
  668.         }
  669.     }
  670.  
  671.     /**
  672.      * Retrieve the directory that downloads will happen in
  673.      * @access private
  674.      * @return string
  675.      */
  676.     function getDownloadDir()
  677.     {
  678.         if (isset($this->_downloadDir)) {
  679.             return $this->_downloadDir;
  680.         }
  681.         $downloaddir = $this->config->get('download_dir');
  682.         if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
  683.             if  (is_dir($downloaddir) && !is_writable($downloaddir)) {
  684.                 $this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
  685.                     '" is not writeable.  Change download_dir config variable to ' .
  686.                     'a writeable dir to avoid this warning');
  687.             }
  688.             if (!class_exists('System')) {
  689.                 require_once 'System.php';
  690.             }
  691.             if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
  692.                 return $downloaddir;
  693.             }
  694.             $this->log(3, '+ tmp dir created at ' . $downloaddir);
  695.         }
  696.         if (!is_writable($downloaddir)) {
  697.             if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
  698.                   !is_writable($downloaddir)) {
  699.                 return PEAR::raiseError('download directory "' . $downloaddir .
  700.                     '" is not writeable.  Change download_dir config variable to ' .
  701.                     'a writeable dir');
  702.             }
  703.         }
  704.         return $this->_downloadDir = $downloaddir;
  705.     }
  706.  
  707.     function setDownloadDir($dir)
  708.     {
  709.         if (!@is_writable($dir)) {
  710.             if (PEAR::isError(System::mkdir(array('-p', $dir)))) {
  711.                 return PEAR::raiseError('download directory "' . $dir .
  712.                     '" is not writeable.  Change download_dir config variable to ' .
  713.                     'a writeable dir');
  714.             }
  715.         }
  716.         $this->_downloadDir = $dir;
  717.     }
  718.  
  719.     // }}}
  720.     // {{{ configSet()
  721.     function configSet($key, $value, $layer = 'user', $channel = false)
  722.     {
  723.         $this->config->set($key, $value, $layer, $channel);
  724.         $this->_preferredState = $this->config->get('preferred_state', null, $channel);
  725.         if (!$this->_preferredState) {
  726.             // don't inadvertantly use a non-set preferred_state
  727.             $this->_preferredState = null;
  728.         }
  729.     }
  730.  
  731.     // }}}
  732.     // {{{ setOptions()
  733.     function setOptions($options)
  734.     {
  735.         $this->_options = $options;
  736.     }
  737.  
  738.     // }}}
  739.     // {{{ setOptions()
  740.     function getOptions()
  741.     {
  742.         return $this->_options;
  743.     }
  744.  
  745.     // }}}
  746.  
  747.     /**
  748.      * For simpler unit-testing
  749.      * @param PEAR_Config
  750.      * @param int
  751.      * @param string
  752.      */
  753.     function &getPackagefileObject(&$c, $d, $t = false)
  754.     {
  755.         if (!class_exists('PEAR_PackageFile')) {
  756.             require_once 'PEAR/PackageFile.php';
  757.         }
  758.         $a = &new PEAR_PackageFile($c, $d, $t);
  759.         return $a;
  760.     }
  761.  
  762.     // {{{ _getPackageDownloadUrl()
  763.  
  764.     /**
  765.      * @param array output of {@link parsePackageName()}
  766.      * @access private
  767.      */
  768.     function _getPackageDownloadUrl($parr)
  769.     {
  770.         $curchannel = $this->config->get('default_channel');
  771.         $this->configSet('default_channel', $parr['channel']);
  772.         // getDownloadURL returns an array.  On error, it only contains information
  773.         // on the latest release as array(version, info).  On success it contains
  774.         // array(version, info, download url string)
  775.         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  776.         if (!$this->_registry->channelExists($parr['channel'])) {
  777.             do {
  778.                 if ($this->config->get('auto_discover')) {
  779.                     if ($this->discover($parr['channel'])) {
  780.                         break;
  781.                     }
  782.                 }
  783.                 $this->configSet('default_channel', $curchannel);
  784.                 return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  785.             } while (false);
  786.         }
  787.         $chan = &$this->_registry->getChannel($parr['channel']);
  788.         if (PEAR::isError($chan)) {
  789.             return $chan;
  790.         }
  791.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  792.         $version = $this->_registry->packageInfo($parr['package'], 'version',
  793.             $parr['channel']);
  794.         PEAR::staticPopErrorHandling();
  795.         $base2 = false;
  796.         if ($chan->supportsREST($this->config->get('preferred_mirror')) &&
  797.               (($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror'))) ||
  798.               ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))))) {
  799.             if ($base2) {
  800.                 $rest = &$this->config->getREST('1.3', $this->_options);
  801.                 $base = $base2;
  802.             } else {
  803.                 $rest = &$this->config->getREST('1.0', $this->_options);
  804.             }
  805.             if (!isset($parr['version']) && !isset($parr['state']) && $version
  806.                   && !PEAR::isError($version)
  807.                   && !isset($this->_options['downloadonly'])) {
  808.                 $url = $rest->getDownloadURL($base, $parr, $state, $version);
  809.             } else {
  810.                 $url = $rest->getDownloadURL($base, $parr, $state, false);
  811.             }
  812.             if (PEAR::isError($url)) {
  813.                 $this->configSet('default_channel', $curchannel);
  814.                 return $url;
  815.             }
  816.             if ($parr['channel'] != $curchannel) {
  817.                 $this->configSet('default_channel', $curchannel);
  818.             }
  819.             if (!is_array($url)) {
  820.                 return $url;
  821.             }
  822.             $url['raw'] = false; // no checking is necessary for REST
  823.             if (!is_array($url['info'])) {
  824.                 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  825.                     'this should never happen');
  826.             }
  827.             if (!isset($this->_options['force']) &&
  828.                   !isset($this->_options['downloadonly']) &&
  829.                   $version &&
  830.                   !PEAR::isError($version) &&
  831.                   !isset($parr['group'])) {
  832.                 if (version_compare($version, $url['version'], '>=')) {
  833.                     return PEAR::raiseError($this->_registry->parsedPackageNameToString(
  834.                         $parr, true) . ' is already installed and is newer than detected ' .
  835.                         'release version ' . $url['version'], -976);
  836.                 }
  837.             }
  838.             if (isset($url['info']['required']) || $url['compatible']) {
  839.                 require_once 'PEAR/PackageFile/v2.php';
  840.                 $pf = new PEAR_PackageFile_v2;
  841.                 $pf->setRawChannel($parr['channel']);
  842.                 if ($url['compatible']) {
  843.                     $pf->setRawCompatible($url['compatible']);
  844.                 }
  845.             } else {
  846.                 require_once 'PEAR/PackageFile/v1.php';
  847.                 $pf = new PEAR_PackageFile_v1;
  848.             }
  849.             $pf->setRawPackage($url['package']);
  850.             $pf->setDeps($url['info']);
  851.             if ($url['compatible']) {
  852.                 $pf->setCompatible($url['compatible']);
  853.             }
  854.             $pf->setRawState($url['stability']);
  855.             $url['info'] = &$pf;
  856.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  857.                 $ext = '.tar';
  858.             } else {
  859.                 $ext = '.tgz';
  860.             }
  861.             if (is_array($url)) {
  862.                 if (isset($url['url'])) {
  863.                     $url['url'] .= $ext;
  864.                 }
  865.             }
  866.             return $url;
  867.         } elseif ($chan->supports('xmlrpc', 'package.getDownloadURL', false, '1.1')) {
  868.             // don't install with the old version information unless we're doing a plain
  869.             // vanilla simple installation.  If the user says to install a particular
  870.             // version or state, ignore the current installed version
  871.             if (!isset($parr['version']) && !isset($parr['state']) && $version
  872.                   && !isset($this->_options['downloadonly'])) {
  873.                 $url = $this->_remote->call('package.getDownloadURL', $parr, $state, $version);
  874.             } else {
  875.                 $url = $this->_remote->call('package.getDownloadURL', $parr, $state);
  876.             }
  877.         } else {
  878.             $url = $this->_remote->call('package.getDownloadURL', $parr, $state);
  879.         }
  880.         if (PEAR::isError($url)) {
  881.             return $url;
  882.         }
  883.         if ($parr['channel'] != $curchannel) {
  884.             $this->configSet('default_channel', $curchannel);
  885.         }
  886.         if (isset($url['__PEAR_ERROR_CLASS__'])) {
  887.             return PEAR::raiseError($url['message']);
  888.         }
  889.         if (!is_array($url)) {
  890.             return $url;
  891.         }
  892.         $url['raw'] = $url['info'];
  893.         if (isset($this->_options['downloadonly'])) {
  894.             $pkg = &$this->getPackagefileObject($this->config, $this->debug);
  895.         } else {
  896.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  897.             if (PEAR::isError($dir = $this->getDownloadDir())) {
  898.                 PEAR::staticPopErrorHandling();
  899.                 return $dir;
  900.             }
  901.             PEAR::staticPopErrorHandling();
  902.             $pkg = &$this->getPackagefileObject($this->config, $this->debug, $dir);
  903.         }
  904.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  905.         $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote');
  906.         PEAR::staticPopErrorHandling();
  907.         if (PEAR::isError($pinfo)) {
  908.             if (!isset($this->_options['soft'])) {
  909.                 $this->log(0, $pinfo->getMessage());
  910.             }
  911.             return PEAR::raiseError('Remote package.xml is not valid - this should never happen');
  912.         }
  913.         $url['info'] = &$pinfo;
  914.         if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  915.             $ext = '.tar';
  916.         } else {
  917.             $ext = '.tgz';
  918.         }
  919.         if (is_array($url)) {
  920.             if (isset($url['url'])) {
  921.                 $url['url'] .= $ext;
  922.             }
  923.         }
  924.         return $url;
  925.     }
  926.     // }}}
  927.     // {{{ getDepPackageDownloadUrl()
  928.  
  929.     /**
  930.      * @param array dependency array
  931.      * @access private
  932.      */
  933.     function _getDepPackageDownloadUrl($dep, $parr)
  934.     {
  935.         $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
  936.         $curchannel = $this->config->get('default_channel');
  937.         if (isset($dep['uri'])) {
  938.             $xsdversion = '2.0';
  939.             $chan = &$this->_registry->getChannel('__uri');
  940.             if (PEAR::isError($chan)) {
  941.                 return $chan;
  942.             }
  943.             $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
  944.             $this->configSet('default_channel', '__uri');
  945.         } else {
  946.             if (isset($dep['channel'])) {
  947.                 $remotechannel = $dep['channel'];
  948.             } else {
  949.                 $remotechannel = 'pear.php.net';
  950.             }
  951.             if (!$this->_registry->channelExists($remotechannel)) {
  952.                 do {
  953.                     if ($this->config->get('auto_discover')) {
  954.                         if ($this->discover($remotechannel)) {
  955.                             break;
  956.                         }
  957.                     }
  958.                     return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
  959.                 } while (false);
  960.             }
  961.             $chan = &$this->_registry->getChannel($remotechannel);
  962.             if (PEAR::isError($chan)) {
  963.                 return $chan;
  964.             }
  965.             $version = $this->_registry->packageInfo($dep['name'], 'version',
  966.                 $remotechannel);
  967.             $this->configSet('default_channel', $remotechannel);
  968.         }
  969.         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
  970.         if (isset($parr['state']) && isset($parr['version'])) {
  971.             unset($parr['state']);
  972.         }
  973.         if (isset($dep['uri'])) {
  974.             $info = &$this->newDownloaderPackage($this);
  975.             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  976.             $err = $info->initialize($dep);
  977.             PEAR::staticPopErrorHandling();
  978.             if (!$err) {
  979.                 // skip parameters that were missed by preferred_state
  980.                 return PEAR::raiseError('Cannot initialize dependency');
  981.             }
  982.             if (PEAR::isError($err)) {
  983.                 if (!isset($this->_options['soft'])) {
  984.                     $this->log(0, $err->getMessage());
  985.                 }
  986.                 if (is_object($info)) {
  987.                     $param = $info->getChannel() . '/' . $info->getPackage();
  988.                 }
  989.                 return PEAR::raiseError('Package "' . $param . '" is not valid');
  990.             }
  991.             return $info;
  992.         } elseif ($chan->supportsREST($this->config->get('preferred_mirror')) &&
  993.               $base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror'))) {
  994.             $rest = &$this->config->getREST('1.0', $this->_options);
  995.             $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
  996.                     $state, $version);
  997.             if (PEAR::isError($url)) {
  998.                 return $url;
  999.             }
  1000.             if ($parr['channel'] != $curchannel) {
  1001.                 $this->configSet('default_channel', $curchannel);
  1002.             }
  1003.             if (!is_array($url)) {
  1004.                 return $url;
  1005.             }
  1006.             $url['raw'] = false; // no checking is necessary for REST
  1007.             if (!is_array($url['info'])) {
  1008.                 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
  1009.                     'this should never happen');
  1010.             }
  1011.             if (isset($url['info']['required'])) {
  1012.                 if (!class_exists('PEAR_PackageFile_v2')) {
  1013.                     require_once 'PEAR/PackageFile/v2.php';
  1014.                 }
  1015.                 $pf = new PEAR_PackageFile_v2;
  1016.                 $pf->setRawChannel($remotechannel);
  1017.             } else {
  1018.                 if (!class_exists('PEAR_PackageFile_v1')) {
  1019.                     require_once 'PEAR/PackageFile/v1.php';
  1020.                 }
  1021.                 $pf = new PEAR_PackageFile_v1;
  1022.             }
  1023.             $pf->setRawPackage($url['package']);
  1024.             $pf->setDeps($url['info']);
  1025.             if ($url['compatible']) {
  1026.                 $pf->setCompatible($url['compatible']);
  1027.             }
  1028.             $pf->setRawState($url['stability']);
  1029.             $url['info'] = &$pf;
  1030.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  1031.                 $ext = '.tar';
  1032.             } else {
  1033.                 $ext = '.tgz';
  1034.             }
  1035.             if (is_array($url)) {
  1036.                 if (isset($url['url'])) {
  1037.                     $url['url'] .= $ext;
  1038.                 }
  1039.             }
  1040.             return $url;
  1041.         } elseif ($chan->supports('xmlrpc', 'package.getDepDownloadURL', false, '1.1')) {
  1042.             if ($version) {
  1043.                 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr,
  1044.                     $state, $version);
  1045.             } else {
  1046.                 $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr,
  1047.                     $state);
  1048.             }
  1049.         } else {
  1050.             $url = $this->_remote->call('package.getDepDownloadURL', $xsdversion, $dep, $parr, $state);
  1051.         }
  1052.         if ($this->config->get('default_channel') != $curchannel) {
  1053.             $this->configSet('default_channel', $curchannel);
  1054.         }
  1055.         if (!is_array($url)) {
  1056.             return $url;
  1057.         }
  1058.         if (isset($url['__PEAR_ERROR_CLASS__'])) {
  1059.             return PEAR::raiseError($url['message']);
  1060.         }
  1061.         $url['raw'] = $url['info'];
  1062.         $pkg = &$this->getPackagefileObject($this->config, $this->debug);
  1063.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  1064.         $pinfo = &$pkg->fromXmlString($url['info'], PEAR_VALIDATE_DOWNLOADING, 'remote');
  1065.         PEAR::staticPopErrorHandling();
  1066.         if (PEAR::isError($pinfo)) {
  1067.             if (!isset($this->_options['soft'])) {
  1068.                 $this->log(0, $pinfo->getMessage());
  1069.             }
  1070.             return PEAR::raiseError('Remote package.xml is not valid - this should never happen');
  1071.         }
  1072.         $url['info'] = &$pinfo;
  1073.         if (is_array($url)) {
  1074.             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
  1075.                 $ext = '.tar';
  1076.             } else {
  1077.                 $ext = '.tgz';
  1078.             }
  1079.             if (isset($url['url'])) {
  1080.                 $url['url'] .= $ext;
  1081.             }
  1082.         }
  1083.         return $url;
  1084.     }
  1085.     // }}}
  1086.     // {{{ getPackageDownloadUrl()
  1087.  
  1088.     /**
  1089.      * @deprecated in favor of _getPackageDownloadUrl
  1090.      */
  1091.     function getPackageDownloadUrl($package, $version = null, $channel = false)
  1092.     {
  1093.         if ($version) {
  1094.             $package .= "-$version";
  1095.         }
  1096.         if ($this === null || $this->_registry === null) {
  1097.             $package = "http://pear.php.net/get/$package";
  1098.         } else {
  1099.             $chan = $this->_registry->getChannel($channel);
  1100.             if (PEAR::isError($chan)) {
  1101.                 return '';
  1102.             }
  1103.             $package = "http://" . $chan->getServer() . "/get/$package";
  1104.         }
  1105.         if (!extension_loaded("zlib")) {
  1106.             $package .= '?uncompress=yes';
  1107.         }
  1108.         return $package;
  1109.     }
  1110.  
  1111.     // }}}
  1112.     // {{{ getDownloadedPackages()
  1113.  
  1114.     /**
  1115.      * Retrieve a list of downloaded packages after a call to {@link download()}.
  1116.      *
  1117.      * Also resets the list of downloaded packages.
  1118.      * @return array
  1119.      */
  1120.     function getDownloadedPackages()
  1121.     {
  1122.         $ret = $this->_downloadedPackages;
  1123.         $this->_downloadedPackages = array();
  1124.         $this->_toDownload = array();
  1125.         return $ret;
  1126.     }
  1127.  
  1128.     // }}}
  1129.     // {{{ _downloadCallback()
  1130.  
  1131.     function _downloadCallback($msg, $params = null)
  1132.     {
  1133.         switch ($msg) {
  1134.             case 'saveas':
  1135.                 $this->log(1, "downloading $params ...");
  1136.                 break;
  1137.             case 'done':
  1138.                 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
  1139.                 break;
  1140.             case 'bytesread':
  1141.                 static $bytes;
  1142.                 if (empty($bytes)) {
  1143.                     $bytes = 0;
  1144.                 }
  1145.                 if (!($bytes % 10240)) {
  1146.                     $this->log(1, '.', false);
  1147.                 }
  1148.                 $bytes += $params;
  1149.                 break;
  1150.             case 'start':
  1151.                 if($params[1] == -1) {
  1152.                     $length = "Unknown size";
  1153.                 } else {
  1154.                     $length = number_format($params[1], 0, '', ',')." bytes";
  1155.                 }
  1156.                 $this->log(1, "Starting to download {$params[0]} ($length)");
  1157.                 break;
  1158.         }
  1159.         if (method_exists($this->ui, '_downloadCallback'))
  1160.             $this->ui->_downloadCallback($msg, $params);
  1161.     }
  1162.  
  1163.     // }}}
  1164.     // {{{ _prependPath($path, $prepend)
  1165.  
  1166.     function _prependPath($path, $prepend)
  1167.     {
  1168.         if (strlen($prepend) > 0) {
  1169.             if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  1170.                 if (preg_match('/^[a-z]:/i', $prepend)) {
  1171.                     $prepend = substr($prepend, 2);
  1172.                 } elseif ($prepend{0} != '\\') {
  1173.                     $prepend = "\\$prepend";
  1174.                 }
  1175.                 $path = substr($path, 0, 2) . $prepend . substr($path, 2);
  1176.             } else {
  1177.                 $path = $prepend . $path;
  1178.             }
  1179.         }
  1180.         return $path;
  1181.     }
  1182.     // }}}
  1183.     // {{{ pushError($errmsg, $code)
  1184.  
  1185.     /**
  1186.      * @param string
  1187.      * @param integer
  1188.      */
  1189.     function pushError($errmsg, $code = -1)
  1190.     {
  1191.         array_push($this->_errorStack, array($errmsg, $code));
  1192.     }
  1193.  
  1194.     // }}}
  1195.     // {{{ getErrorMsgs()
  1196.  
  1197.     function getErrorMsgs()
  1198.     {
  1199.         $msgs = array();
  1200.         $errs = $this->_errorStack;
  1201.         foreach ($errs as $err) {
  1202.             $msgs[] = $err[0];
  1203.         }
  1204.         $this->_errorStack = array();
  1205.         return $msgs;
  1206.     }
  1207.  
  1208.     // }}}
  1209.  
  1210.     /**
  1211.      * for BC
  1212.      */
  1213.     function sortPkgDeps(&$packages, $uninstall = false)
  1214.     {
  1215.         $uninstall ? 
  1216.             $this->sortPackagesForUninstall($packages) :
  1217.             $this->sortPackagesForInstall($packages);
  1218.     }
  1219.  
  1220.     /**
  1221.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1222.      *
  1223.      * This uses the topological sort method from graph theory, and the
  1224.      * Structures_Graph package to properly sort dependencies for installation.
  1225.      * @param array an array of downloaded PEAR_Downloader_Packages
  1226.      * @return array array of array(packagefilename, package.xml contents)
  1227.      */
  1228.     function sortPackagesForInstall(&$packages)
  1229.     {
  1230.         require_once 'Structures/Graph.php';
  1231.         require_once 'Structures/Graph/Node.php';
  1232.         require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
  1233.         $depgraph = new Structures_Graph(true);
  1234.         $nodes = array();
  1235.         $reg = &$this->config->getRegistry();
  1236.         foreach ($packages as $i => $package) {
  1237.             $pname = $reg->parsedPackageNameToString(
  1238.                 array(
  1239.                     'channel' => $package->getChannel(),
  1240.                     'package' => strtolower($package->getPackage()),
  1241.                 ));
  1242.             $nodes[$pname] = new Structures_Graph_Node;
  1243.             $nodes[$pname]->setData($packages[$i]);
  1244.             $depgraph->addNode($nodes[$pname]);
  1245.         }
  1246.         $deplinks = array();
  1247.         foreach ($nodes as $package => $node) {
  1248.             $pf = &$node->getData();
  1249.             $pdeps = $pf->getDeps(true);
  1250.             if (!$pdeps) {
  1251.                 continue;
  1252.             }
  1253.             if ($pf->getPackagexmlVersion() == '1.0') {
  1254.                 foreach ($pdeps as $dep) {
  1255.                     if ($dep['type'] != 'pkg' ||
  1256.                           (isset($dep['optional']) && $dep['optional'] == 'yes')) {
  1257.                         continue;
  1258.                     }
  1259.                     $dname = $reg->parsedPackageNameToString(
  1260.                           array(
  1261.                               'channel' => 'pear.php.net',
  1262.                               'package' => strtolower($dep['name']),
  1263.                           ));
  1264.                     if (isset($nodes[$dname]))
  1265.                     {
  1266.                         if (!isset($deplinks[$dname])) {
  1267.                             $deplinks[$dname] = array();
  1268.                         }
  1269.                         $deplinks[$dname][$package] = 1;
  1270.                         // dependency is in installed packages
  1271.                         continue;
  1272.                     }
  1273.                     $dname = $reg->parsedPackageNameToString(
  1274.                           array(
  1275.                               'channel' => 'pecl.php.net',
  1276.                               'package' => strtolower($dep['name']),
  1277.                           ));
  1278.                     if (isset($nodes[$dname]))
  1279.                     {
  1280.                         if (!isset($deplinks[$dname])) {
  1281.                             $deplinks[$dname] = array();
  1282.                         }
  1283.                         $deplinks[$dname][$package] = 1;
  1284.                         // dependency is in installed packages
  1285.                         continue;
  1286.                     }
  1287.                 }
  1288.             } else {
  1289.                 // the only ordering we care about is:
  1290.                 // 1) subpackages must be installed before packages that depend on them
  1291.                 // 2) required deps must be installed before packages that depend on them
  1292.                 if (isset($pdeps['required']['subpackage'])) {
  1293.                     $t = $pdeps['required']['subpackage'];
  1294.                     if (!isset($t[0])) {
  1295.                         $t = array($t);
  1296.                     }
  1297.                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1298.                 }
  1299.                 if (isset($pdeps['group'])) {
  1300.                     if (!isset($pdeps['group'][0])) {
  1301.                         $pdeps['group'] = array($pdeps['group']);
  1302.                     }
  1303.                     foreach ($pdeps['group'] as $group) {
  1304.                         if (isset($group['subpackage'])) {
  1305.                             $t = $group['subpackage'];
  1306.                             if (!isset($t[0])) {
  1307.                                 $t = array($t);
  1308.                             }
  1309.                             $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1310.                         }
  1311.                     }
  1312.                 }
  1313.                 if (isset($pdeps['optional']['subpackage'])) {
  1314.                     $t = $pdeps['optional']['subpackage'];
  1315.                     if (!isset($t[0])) {
  1316.                         $t = array($t);
  1317.                     }
  1318.                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1319.                 }
  1320.                 if (isset($pdeps['required']['package'])) {
  1321.                     $t = $pdeps['required']['package'];
  1322.                     if (!isset($t[0])) {
  1323.                         $t = array($t);
  1324.                     }
  1325.                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1326.                 }
  1327.                 if (isset($pdeps['group'])) {
  1328.                     if (!isset($pdeps['group'][0])) {
  1329.                         $pdeps['group'] = array($pdeps['group']);
  1330.                     }
  1331.                     foreach ($pdeps['group'] as $group) {
  1332.                         if (isset($group['package'])) {
  1333.                             $t = $group['package'];
  1334.                             if (!isset($t[0])) {
  1335.                                 $t = array($t);
  1336.                             }
  1337.                             $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
  1338.                         }
  1339.                     }
  1340.                 }
  1341.             }
  1342.         }
  1343.         $this->_detectDepCycle($deplinks);
  1344.         foreach ($deplinks as $dependent => $parents) {
  1345.             foreach ($parents as $parent => $unused) {
  1346.                 $nodes[$dependent]->connectTo($nodes[$parent]);
  1347.             }
  1348.         }
  1349.         $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
  1350.         $ret = array();
  1351.         for ($i = 0; $i < count($installOrder); $i++) {
  1352.             foreach ($installOrder[$i] as $index => $sortedpackage) {
  1353.                 $data = &$installOrder[$i][$index]->getData();
  1354.                 $ret[] = &$nodes[$reg->parsedPackageNameToString(
  1355.                           array(
  1356.                               'channel' => $data->getChannel(),
  1357.                               'package' => strtolower($data->getPackage()),
  1358.                           ))]->getData();
  1359.             }
  1360.         }
  1361.         $packages = $ret;
  1362.         return;
  1363.     }
  1364.  
  1365.     /**
  1366.      * Detect recursive links between dependencies and break the cycles
  1367.      *
  1368.      * @param array
  1369.      * @access private
  1370.      */
  1371.     function _detectDepCycle(&$deplinks)
  1372.     {
  1373.         do {
  1374.             $keepgoing = false;
  1375.             foreach ($deplinks as $dep => $parents) {
  1376.                 foreach ($parents as $parent => $unused) {
  1377.                     // reset the parent cycle detector
  1378.                     $this->_testCycle(null, null, null);
  1379.                     if ($this->_testCycle($dep, $deplinks, $parent)) {
  1380.                         $keepgoing = true;
  1381.                         unset($deplinks[$dep][$parent]);
  1382.                         if (count($deplinks[$dep]) == 0) {
  1383.                             unset($deplinks[$dep]);
  1384.                         }
  1385.                         continue 3;
  1386.                     }
  1387.                 }
  1388.             }
  1389.         } while ($keepgoing);
  1390.     }
  1391.  
  1392.     function _testCycle($test, $deplinks, $dep)
  1393.     {
  1394.         static $visited = array();
  1395.         if ($test === null) {
  1396.             $visited = array();
  1397.             return;
  1398.         }
  1399.         // this happens when a parent has a dep cycle on another dependency
  1400.         // but the child is not part of the cycle
  1401.         if (isset($visited[$dep])) {
  1402.             return false;
  1403.         }
  1404.         $visited[$dep] = 1;
  1405.         if ($test == $dep) {
  1406.             return true;
  1407.         }
  1408.         if (isset($deplinks[$dep])) {
  1409.             if (in_array($test, array_keys($deplinks[$dep]), true)) {
  1410.                 return true;
  1411.             }
  1412.             foreach ($deplinks[$dep] as $parent => $unused) {
  1413.                 if ($this->_testCycle($test, $deplinks, $parent)) {
  1414.                     return true;
  1415.                 }
  1416.             }
  1417.         }
  1418.         return false;
  1419.     }
  1420.  
  1421.     /**
  1422.      * Set up the dependency for installation parsing
  1423.      *
  1424.      * @param array $t dependency information
  1425.      * @param PEAR_Registry $reg
  1426.      * @param array $deplinks list of dependency links already established
  1427.      * @param array $nodes all existing package nodes
  1428.      * @param string $package parent package name
  1429.      * @access private
  1430.      */
  1431.     function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
  1432.     {
  1433.         foreach ($t as $dep) {
  1434.             $depchannel = !isset($dep['channel']) ?
  1435.                 '__uri': $dep['channel'];
  1436.             $dname = $reg->parsedPackageNameToString(
  1437.                   array(
  1438.                       'channel' => $depchannel,
  1439.                       'package' => strtolower($dep['name']),
  1440.                   ));
  1441.             if (isset($nodes[$dname]))
  1442.             {
  1443.                 if (!isset($deplinks[$dname])) {
  1444.                     $deplinks[$dname] = array();
  1445.                 }
  1446.                 $deplinks[$dname][$package] = 1;
  1447.             }
  1448.         }
  1449.     }
  1450.  
  1451.     function _dependsOn($a, $b)
  1452.     {
  1453.         return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()),
  1454.             $b);
  1455.     }
  1456.  
  1457.     function _checkDepTree($channel, $package, $b, $checked = array())
  1458.     {
  1459.         $checked[$channel][$package] = true;
  1460.         if (!isset($this->_depTree[$channel][$package])) {
  1461.             return false;
  1462.         }
  1463.         if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
  1464.               [strtolower($b->getPackage())])) {
  1465.             return true;
  1466.         }
  1467.         foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
  1468.             foreach ($packages as $pa => $true) {
  1469.                 if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
  1470.                     return true;
  1471.                 }
  1472.             }
  1473.         }
  1474.         return false;
  1475.     }
  1476.  
  1477.     function _sortInstall($a, $b)
  1478.     {
  1479.         if (!$a->getDeps() && !$b->getDeps()) {
  1480.             return 0; // neither package has dependencies, order is insignificant
  1481.         }
  1482.         if ($a->getDeps() && !$b->getDeps()) {
  1483.             return 1; // $a must be installed after $b because $a has dependencies
  1484.         }
  1485.         if (!$a->getDeps() && $b->getDeps()) {
  1486.             return -1; // $b must be installed after $a because $b has dependencies
  1487.         }
  1488.         // both packages have dependencies
  1489.         if ($this->_dependsOn($a, $b)) {
  1490.             return 1;
  1491.         }
  1492.         if ($this->_dependsOn($b, $a)) {
  1493.             return -1;
  1494.         }
  1495.         return 0;
  1496.     }
  1497.  
  1498.     /**
  1499.      * Download a file through HTTP.  Considers suggested file name in
  1500.      * Content-disposition: header and can run a callback function for
  1501.      * different events.  The callback will be called with two
  1502.      * parameters: the callback type, and parameters.  The implemented
  1503.      * callback types are:
  1504.      *
  1505.      *  'setup'       called at the very beginning, parameter is a UI object
  1506.      *                that should be used for all output
  1507.      *  'message'     the parameter is a string with an informational message
  1508.      *  'saveas'      may be used to save with a different file name, the
  1509.      *                parameter is the filename that is about to be used.
  1510.      *                If a 'saveas' callback returns a non-empty string,
  1511.      *                that file name will be used as the filename instead.
  1512.      *                Note that $save_dir will not be affected by this, only
  1513.      *                the basename of the file.
  1514.      *  'start'       download is starting, parameter is number of bytes
  1515.      *                that are expected, or -1 if unknown
  1516.      *  'bytesread'   parameter is the number of bytes read so far
  1517.      *  'done'        download is complete, parameter is the total number
  1518.      *                of bytes read
  1519.      *  'connfailed'  if the TCP/SSL connection fails, this callback is called
  1520.      *                with array(host,port,errno,errmsg)
  1521.      *  'writefailed' if writing to disk fails, this callback is called
  1522.      *                with array(destfile,errmsg)
  1523.      *
  1524.      * If an HTTP proxy has been configured (http_proxy PEAR_Config
  1525.      * setting), the proxy will be used.
  1526.      *
  1527.      * @param string  $url       the URL to download
  1528.      * @param object  $ui        PEAR_Frontend_* instance
  1529.      * @param object  $config    PEAR_Config instance
  1530.      * @param string  $save_dir  directory to save file in
  1531.      * @param mixed   $callback  function/method to call for status
  1532.      *                           updates
  1533.      * @param false|string|array $lastmodified header values to check against for caching
  1534.      *                           use false to return the header values from this download
  1535.      * @param false|array $accept Accept headers to send
  1536.      * @param false|string $channel Channel to use for retrieving authentication
  1537.      * @return string|array  Returns the full path of the downloaded file or a PEAR
  1538.      *                       error on failure.  If the error is caused by
  1539.      *                       socket-related errors, the error object will
  1540.      *                       have the fsockopen error code available through
  1541.      *                       getCode().  If caching is requested, then return the header
  1542.      *                       values.
  1543.      *
  1544.      * @access public
  1545.      */
  1546.     function downloadHttp($url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
  1547.                           $accept = false, $channel = false)
  1548.     {
  1549.         static $redirect = 0;
  1550.         // allways reset , so we are clean case of error
  1551.         $wasredirect = $redirect;
  1552.         $redirect = 0;
  1553.         if ($callback) {
  1554.             call_user_func($callback, 'setup', array(&$ui));
  1555.         }
  1556.         $info = parse_url($url);
  1557.         if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
  1558.             return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
  1559.         }
  1560.         if (!isset($info['host'])) {
  1561.             return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
  1562.         } else {
  1563.             $host = isset($info['host']) ? $info['host'] : null;
  1564.             $port = isset($info['port']) ? $info['port'] : null;
  1565.             $path = isset($info['path']) ? $info['path'] : null;
  1566.         }
  1567.         if (isset($this)) {
  1568.             $config = &$this->config;
  1569.         } else {
  1570.             $config = &PEAR_Config::singleton();
  1571.         }
  1572.         $proxy_host = $proxy_port = $proxy_user = $proxy_pass = '';
  1573.         if ($config->get('http_proxy') && 
  1574.               $proxy = parse_url($config->get('http_proxy'))) {
  1575.             $proxy_host = isset($proxy['host']) ? $proxy['host'] : null;
  1576.             if (isset($proxy['scheme']) && $proxy['scheme'] == 'https') {
  1577.                 $proxy_host = 'ssl://' . $proxy_host;
  1578.             }
  1579.             $proxy_port = isset($proxy['port']) ? $proxy['port'] : 8080;
  1580.             $proxy_user = isset($proxy['user']) ? urldecode($proxy['user']) : null;
  1581.             $proxy_pass = isset($proxy['pass']) ? urldecode($proxy['pass']) : null;
  1582.  
  1583.             if ($callback) {
  1584.                 call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
  1585.             }
  1586.         }
  1587.         if (empty($port)) {
  1588.             if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1589.                 $port = 443;
  1590.             } else {
  1591.                 $port = 80;
  1592.             }
  1593.         }
  1594.         if ($proxy_host != '') {
  1595.             $fp = @fsockopen($proxy_host, $proxy_port, $errno, $errstr);
  1596.             if (!$fp) {
  1597.                 if ($callback) {
  1598.                     call_user_func($callback, 'connfailed', array($proxy_host, $proxy_port,
  1599.                                                                   $errno, $errstr));
  1600.                 }
  1601.                 return PEAR::raiseError("Connection to `$proxy_host:$proxy_port' failed: $errstr", $errno);
  1602.             }
  1603.             if ($lastmodified === false || $lastmodified) {
  1604.                 $request = "GET $url HTTP/1.1\r\n";
  1605.             } else {
  1606.                 $request = "GET $url HTTP/1.0\r\n";
  1607.             }
  1608.         } else {
  1609.             if (isset($info['scheme']) && $info['scheme'] == 'https') {
  1610.                 $host = 'ssl://' . $host;
  1611.             }
  1612.             $fp = @fsockopen($host, $port, $errno, $errstr);
  1613.             if (!$fp) {
  1614.                 if ($callback) {
  1615.                     call_user_func($callback, 'connfailed', array($host, $port,
  1616.                                                                   $errno, $errstr));
  1617.                 }
  1618.                 return PEAR::raiseError("Connection to `$host:$port' failed: $errstr", $errno);
  1619.             }
  1620.             if ($lastmodified === false || $lastmodified) {
  1621.                 $request = "GET $path HTTP/1.1\r\n";
  1622.                 $request .= "Host: $host:$port\r\n";
  1623.             } else {
  1624.                 $request = "GET $path HTTP/1.0\r\n";
  1625.                 $request .= "Host: $host\r\n";
  1626.             }
  1627.         }
  1628.         $ifmodifiedsince = '';
  1629.         if (is_array($lastmodified)) {
  1630.             if (isset($lastmodified['Last-Modified'])) {
  1631.                 $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
  1632.             }
  1633.             if (isset($lastmodified['ETag'])) {
  1634.                 $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
  1635.             }
  1636.         } else {
  1637.             $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
  1638.         }
  1639.         $request .= $ifmodifiedsince . "User-Agent: PEAR/1.7.1/PHP/" .
  1640.             PHP_VERSION . "\r\n";
  1641.         if (isset($this)) { // only pass in authentication for non-static calls
  1642.             $username = $config->get('username', null, $channel);
  1643.             $password = $config->get('password', null, $channel);
  1644.             if ($username && $password) {
  1645.                 $tmp = base64_encode("$username:$password");
  1646.                 $request .= "Authorization: Basic $tmp\r\n";
  1647.             }
  1648.         }
  1649.         if ($proxy_host != '' && $proxy_user != '') {
  1650.             $request .= 'Proxy-Authorization: Basic ' .
  1651.                 base64_encode($proxy_user . ':' . $proxy_pass) . "\r\n";
  1652.         }
  1653.         if ($accept) {
  1654.             $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
  1655.         }
  1656.         $request .= "Connection: close\r\n";
  1657.         $request .= "\r\n";
  1658.         fwrite($fp, $request);
  1659.         $headers = array();
  1660.         $reply = 0;
  1661.         while (trim($line = fgets($fp, 1024))) {
  1662.             if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
  1663.                 $headers[strtolower($matches[1])] = trim($matches[2]);
  1664.             } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
  1665.                 $reply = (int) $matches[1];
  1666.                 if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
  1667.                     return false;
  1668.                 }
  1669.                 if (! in_array($reply, array(200, 301, 302, 303, 305, 307))) {
  1670.                     return PEAR::raiseError("File http://$host:$port$path not valid (received: $line)");
  1671.                 }
  1672.             }
  1673.         }
  1674.         if ($reply != 200) {
  1675.             if (isset($headers['location'])) {
  1676.                 if ($wasredirect < 5) {
  1677.                     $redirect = $wasredirect + 1;
  1678.                     return $this->downloadHttp($headers['location'],
  1679.                             $ui, $save_dir, $callback, $lastmodified, $accept);
  1680.                 } else {
  1681.                     return PEAR::raiseError("File http://$host:$port$path not valid (redirection looped more than 5 times)");
  1682.                 }
  1683.             } else {
  1684.                 return PEAR::raiseError("File http://$host:$port$path not valid (redirected but no location)");
  1685.             }
  1686.         }
  1687.         if (isset($headers['content-disposition']) &&
  1688.             preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
  1689.             $save_as = basename($matches[1]);
  1690.         } else {
  1691.             $save_as = basename($url);
  1692.         }
  1693.         if ($callback) {
  1694.             $tmp = call_user_func($callback, 'saveas', $save_as);
  1695.             if ($tmp) {
  1696.                 $save_as = $tmp;
  1697.             }
  1698.         }
  1699.         $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
  1700.         if (!$wp = @fopen($dest_file, 'wb')) {
  1701.             fclose($fp);
  1702.             if ($callback) {
  1703.                 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1704.             }
  1705.             return PEAR::raiseError("could not open $dest_file for writing");
  1706.         }
  1707.         if (isset($headers['content-length'])) {
  1708.             $length = $headers['content-length'];
  1709.         } else {
  1710.             $length = -1;
  1711.         }
  1712.         $bytes = 0;
  1713.         if ($callback) {
  1714.             call_user_func($callback, 'start', array(basename($dest_file), $length));
  1715.         }
  1716.         while ($data = fread($fp, 1024)) {
  1717.             $bytes += strlen($data);
  1718.             if ($callback) {
  1719.                 call_user_func($callback, 'bytesread', $bytes);
  1720.             }
  1721.             if (!@fwrite($wp, $data)) {
  1722.                 fclose($fp);
  1723.                 if ($callback) {
  1724.                     call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
  1725.                 }
  1726.                 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
  1727.             }
  1728.         }
  1729.         fclose($fp);
  1730.         fclose($wp);
  1731.         if ($callback) {
  1732.             call_user_func($callback, 'done', $bytes);
  1733.         }
  1734.         if ($lastmodified === false || $lastmodified) {
  1735.             if (isset($headers['etag'])) {
  1736.                 $lastmodified = array('ETag' => $headers['etag']);
  1737.             }
  1738.             if (isset($headers['last-modified'])) {
  1739.                 if (is_array($lastmodified)) {
  1740.                     $lastmodified['Last-Modified'] = $headers['last-modified'];
  1741.                 } else {
  1742.                     $lastmodified = $headers['last-modified'];
  1743.                 }
  1744.             }
  1745.             return array($dest_file, $lastmodified, $headers);
  1746.         }
  1747.         return $dest_file;
  1748.     }
  1749. }
  1750. // }}}
  1751.  
  1752. ?>
  1753.