]> granicus.if.org Git - php/commitdiff
MFH 4.3.4RC1
authorPierre Joye <pajoye@php.net>
Mon, 29 Sep 2003 14:06:44 +0000 (14:06 +0000)
committerPierre Joye <pajoye@php.net>
Mon, 29 Sep 2003 14:06:44 +0000 (14:06 +0000)
12 files changed:
pear/OS/Guess.php
pear/PEAR.php
pear/PEAR/Command/Install.php
pear/PEAR/Common.php
pear/PEAR/Dependency.php
pear/PEAR/Frontend/CLI.php
pear/PEAR/Installer.php
pear/PEAR/Registry.php
pear/System.php
pear/package-PEAR.xml
pear/package.dtd
pear/scripts/pear.bat

index 7cbd988af05f6127c930bb21a5f834dd9176a2b7..c6852fef2576031dabb78938613ce2a383ed7c75 100644 (file)
@@ -167,7 +167,7 @@ class OS_Guess
         $cpp = popen("/usr/bin/cpp $tmpfile", "r");
         $major = $minor = 0;
         while ($line = fgets($cpp, 1024)) {
-            if ($line{0} == '#') {
+            if ($line{0} == '#' || trim($line) == '') {
                 continue;
             }
             if (list($major, $minor) = explode(' ', trim($line))) {
index ec98d5576f64fd1722829a497bc97a559ff9aff7..da400826049ee07ffe8eab2e8cb181e25569158c 100644 (file)
@@ -289,7 +289,8 @@ class PEAR
 
     function setErrorHandling($mode = null, $options = null)
     {
-        if (isset($this)) {
+        if (isset($this) &&
+            (get_class($this) == 'pear' || is_subclass_of($this, 'pear'))) {
             $setmode     = &$this->_default_error_mode;
             $setoptions  = &$this->_default_error_options;
         } else {
@@ -310,9 +311,8 @@ class PEAR
 
             case PEAR_ERROR_CALLBACK:
                 $setmode = $mode;
-                if ((is_string($options) && function_exists($options)) ||
-                    (is_array($options) && method_exists(@$options[0], @$options[1])))
-                {
+                // class/object method callback
+                if (is_callable($options)) {
                     $setoptions = $options;
                 } else {
                     trigger_error("invalid error callback", E_USER_WARNING);
@@ -566,7 +566,8 @@ class PEAR
     function pushErrorHandling($mode, $options = null)
     {
         $stack = &$GLOBALS['_PEAR_error_handler_stack'];
-        if (isset($this)) {
+        if (isset($this) &&
+            (get_class($this) == 'pear' || is_subclass_of($this, 'pear'))) {
             $def_mode    = &$this->_default_error_mode;
             $def_options = &$this->_default_error_options;
         } else {
@@ -575,7 +576,8 @@ class PEAR
         }
         $stack[] = array($def_mode, $def_options);
 
-        if (isset($this)) {
+        if (isset($this) &&
+            (get_class($this) == 'pear' || is_subclass_of($this, 'pear'))) {
             $this->setErrorHandling($mode, $options);
         } else {
             PEAR::setErrorHandling($mode, $options);
@@ -600,7 +602,8 @@ class PEAR
         array_pop($stack);
         list($mode, $options) = $stack[sizeof($stack) - 1];
         array_pop($stack);
-        if (isset($this)) {
+        if (isset($this) &&
+            (get_class($this) == 'pear' || is_subclass_of($this, 'pear'))) {
             $this->setErrorHandling($mode, $options);
         } else {
             PEAR::setErrorHandling($mode, $options);
index 8026a7bfb19151ee3b3456b269be5a990deedd56..9cac11a05859c2e8ce0a023a20fe2890d56d128c 100644 (file)
@@ -20,7 +20,6 @@
 
 require_once "PEAR/Command/Common.php";
 require_once "PEAR/Installer.php";
-require_once "Console/Getopt.php";
 
 /**
  * PEAR commands for installation or deinstallation/upgrading of
@@ -69,6 +68,14 @@ class PEAR_Command_Install extends PEAR_Command_Common
                 'ignore-errors' => array(
                     'doc' => 'force install even if there were errors',
                     ),
+                'alldeps' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'install all required and optional dependencies',
+                    ),
+                'onlyreqdeps' => array(
+                    'shortopt' => 'o',
+                    'doc' => 'install all required dependencies',
+                    ),
                 ),
             'doc' => '<package> ...
 Installs one or more PEAR packages.  You can specify a package to
@@ -123,6 +130,14 @@ four ways of specifying packages.
                 'ignore-errors' => array(
                     'doc' => 'force install even if there were errors',
                     ),
+                'alldeps' => array(
+                    'shortopt' => 'a',
+                    'doc' => 'install all required and optional dependencies',
+                    ),
+                'onlyreqdeps' => array(
+                    'shortopt' => 'o',
+                    'doc' => 'install all required dependencies',
+                    ),
                 ),
             'doc' => '<package> ...
 Upgrades one or more PEAR packages.  See documentation for the
@@ -240,7 +255,7 @@ package if needed.
             $this->installer = &new PEAR_Installer($this->ui);
         }
         if ($command == 'upgrade') {
-            $options[$command] = true;
+            $options['upgrade'] = true;
         }
         if ($command == 'upgrade-all') {
             include_once "PEAR/Remote.php";
@@ -273,9 +288,25 @@ package if needed.
                 $this->ui->outputData(array('data' => "Will upgrade $package"), $command);
             }
         }
-        foreach ($params as $pkg) {
-            $bn = basename($pkg);
-            $info = $this->installer->install($pkg, $options, $this->config);
+        $errors = array();
+        $downloaded = array();
+        $this->installer->download($params, $options, $this->config, $downloaded,
+                                   $errors);
+        if ($command != 'upgrade-all') {
+            for ($i = 0; $i < count($params); $i++) {
+                $params[$i] = $this->installer->extractDownloadFileName($params[$i], $_tmp);
+            }
+        }
+        if (count($errors)) {
+            $err['data'] = array($errors);
+            $err['headline'] = 'Install Errors';
+            $this->ui->outputData($err);
+            return $this->raiseError("$command failed");
+        }
+        $this->installer->sortPkgDeps($downloaded);
+        foreach ($downloaded as $pkg) {
+            $bn = basename($pkg['file']);
+            $info = $this->installer->install($pkg['file'], $options, $this->config);
             if (is_array($info)) {
                 if ($this->config->get('verbose') > 0) {
                     $label = "$info[package] $info[version]";
@@ -303,6 +334,24 @@ package if needed.
         if (sizeof($params) < 1) {
             return $this->raiseError("Please supply the package(s) you want to uninstall");
         }
+        include_once 'PEAR/Registry.php';
+        $reg = new PEAR_Registry($this->config->get('php_dir'));
+        $newparams = array();
+        $badparams = array();
+        foreach ($params as $pkg) {
+            $info = $reg->packageInfo($pkg);
+            if ($info === null) {
+                $badparams[] = $pkg;
+            } else {
+                $newparams[] = $info;
+            }
+        }
+        PEAR_Common::sortPkgDeps($newparams, true);
+        $params = array();
+        foreach($newparams as $info) {
+            $params[] = $info['info']['package'];
+        }
+        $params = array_merge($params, $badparams);
         foreach ($params as $pkg) {
             if ($this->installer->uninstall($pkg, $options)) {
                 if ($this->config->get('verbose') > 0) {
index 81f88b66431bf16fe4215611f269dc540174800e..e4cfa7515f9d08c2bb3d45e8717ab283e9f93494 100644 (file)
@@ -26,7 +26,10 @@ require_once 'PEAR/Config.php';
 
 // {{{ constants and globals
 
-define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^([A-Z][a-zA-Z0-9_]+|[a-z][a-z0-9_]+)$/');
+define('PEAR_COMMON_PACKAGE_NAME_PREG', '/^[A-Za-z][a-zA-Z0-9_]+$/');
+
+// XXX far from perfect :-)
+define('PEAR_COMMON_PACKAGE_DOWNLOAD_PREG', '/^([A-Za-z][a-zA-Z0-9_]+)(-([.0-9a-zA-Z]+))?$/');
 
 /**
  * List of temporary files and directories registered by
@@ -214,11 +217,11 @@ class PEAR_Common extends PEAR
      *
      * @access public
      */
-    function log($level, $msg)
+    function log($level, $msg, $append_crlf = true)
     {
         if ($this->debug >= $level) {
             if (is_object($this->ui)) {
-                $this->ui->log($msg);
+                $this->ui->log($msg, $append_crlf);
             } else {
                 print "$msg\n";
             }
@@ -329,6 +332,11 @@ class PEAR_Common extends PEAR
                 $elem_start = '_element_start_'. $vs;
                 $elem_end = '_element_end_'. $vs;
                 $cdata = '_pkginfo_cdata_'. $vs;
+                if (!method_exists($this, $elem_start) ||
+                      !method_exists($this, $elem_end) ||
+                      !method_exists($this, $cdata)) {
+                    $this->raiseError("No handlers for package.xml version $attribs[version]");
+                }
                 xml_set_element_handler($xp, $elem_start, $elem_end);
                 xml_set_character_data_handler($xp, $cdata);
                 break;
@@ -699,7 +707,7 @@ class PEAR_Common extends PEAR
                 break;
             }
         }
-        $tmpdir = System::mkTemp('-d pear');
+        $tmpdir = System::mkTemp(array('-d', 'pear'));
         $this->addTempFile($tmpdir);
         if (!$xml || !$tar->extractList(array($xml), $tmpdir)) {
             return $this->raiseError('could not extract the package.xml file');
@@ -924,6 +932,9 @@ class PEAR_Common extends PEAR
                 if (isset($dep['version'])) {
                     $ret .= " version=\"$dep[version]\"";
                 }
+                if (isset($dep['optional'])) {
+                    $ret .= " optional=\"$dep[optional]\"";
+                }
                 if (isset($dep['name'])) {
                     $ret .= ">$dep[name]</dep>\n";
                 } else {
@@ -1081,7 +1092,7 @@ class PEAR_Common extends PEAR
             $i = 1;
             foreach ($info['deps'] as $d) {
                 if (empty($d['type'])) {
-                    $errors[] = "depenency $i: missing type";
+                    $errors[] = "dependency $i: missing type";
                 } elseif (!in_array($d['type'], $_PEAR_Common_dependency_types)) {
                     $errors[] = "dependency $i: invalid type, should be one of: ".implode(' ', $_PEAR_Common_depenency_types);
                 }
@@ -1090,6 +1101,11 @@ class PEAR_Common extends PEAR
                 } elseif (!in_array($d['rel'], $_PEAR_Common_dependency_relations)) {
                     $errors[] = "dependency $i: invalid relation, should be one of: ".implode(' ', $_PEAR_Common_dependency_relations);
                 }
+                if (!empty($d['optional'])) {
+                    if (!in_array($d['optional'], array('yes', 'no'))) {
+                        $errors[] = "dependency $i: invalid relation optional attribute, should be one of: yes no";
+                    }
+                }
                 if ($d['rel'] != 'has' && empty($d['version'])) {
                     $warnings[] = "dependency $i: missing version";
                 } elseif ($d['rel'] == 'has' && !empty($d['version'])) {
@@ -1349,6 +1365,30 @@ class PEAR_Common extends PEAR
             );
     }
 
+    // }}}
+    // {{{  betterStates()
+
+    /**
+     * Return an array containing all of the states that are more stable than
+     * or equal to the passed in state
+     *
+     * @param string Release state
+     * @param boolean Determines whether to include $state in the list
+     * @return false|array False if $state is not a valid release state
+     */
+    function betterStates($state, $include = false)
+    {
+        static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
+        $i = array_search($state, $states);
+        if ($i === false) {
+            return false;
+        }
+        if ($include) {
+            $i--;
+        }
+        return array_slice($states, $i + 1);
+    }
+
     // }}}
     // {{{ detectDependencies()
 
@@ -1659,7 +1699,7 @@ class PEAR_Common extends PEAR
         }
         $bytes = 0;
         if ($callback) {
-            call_user_func($callback, 'start', $length);
+            call_user_func($callback, 'start', array(basename($dest_file), $length));
         }
         while ($data = @fread($fp, 1024)) {
             $bytes += strlen($data);
@@ -1682,6 +1722,173 @@ class PEAR_Common extends PEAR
         return $dest_file;
     }
 
+    // }}}
+    // {{{ sortPkgDeps()
+
+    /**
+     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+     *
+     * It also removes duplicate dependencies
+     * @param array
+     * @param boolean Sort packages in reverse order if true
+     * @return array array of array(packagefilename, package.xml contents)
+     */
+    function sortPkgDeps(&$packages, $uninstall = false)
+    {
+        $ret = array();
+        if ($uninstall) {
+            foreach($packages as $packageinfo) {
+                $ret[] = array('info' => $packageinfo);
+            }
+        } else {
+            foreach($packages as $packagefile) {
+                if (!is_array($packagefile)) {
+                    $ret[] = array('file' => $packagefile,
+                                   'info' => $a = $this->infoFromAny($packagefile),
+                                   'pkg' => $a['package']);
+                } else {
+                    $ret[] = $packagefile;
+                }
+            }
+        }
+        $checkdupes = array();
+        $newret = array();
+        foreach($ret as $i => $p) {
+            if (!isset($checkdupes[$p['info']['package']])) {
+                $checkdupes[$p['info']['package']][] = $i;
+                $newret[] = $p;
+            }
+        }
+
+        $func = $uninstall ? '_sortPkgDepsRev' : '_sortPkgDeps';
+        usort($newret, array('PEAR_Common', $func));
+        $packages = $newret;
+    }
+
+    // }}}
+    // {{{ _sortPkgDeps()
+
+    /**
+     * Compare two package's package.xml, and sort
+     * so that dependencies are installed first
+     *
+     * This is a crude compare, real dependency checking is done on install.
+     * The only purpose this serves is to make the command-line
+     * order-independent (you can list a dependent package first, and
+     * installation occurs in the order required)
+     * @access private
+     */
+    function _sortPkgDeps($p1, $p2)
+    {
+        $p1name = $p1['info']['package'];
+        $p2name = $p2['info']['package'];
+        $p1deps = PEAR_Common::_getPkgDeps($p1);
+        $p2deps = PEAR_Common::_getPkgDeps($p2);
+        if (!count($p1deps) && !count($p2deps)) {
+            return 0; // order makes no difference
+        }
+        if (!count($p1deps)) {
+            return -1; // package 2 has dependencies, package 1 doesn't
+        }
+        if (!count($p2deps)) {
+            return 1; // package 2 has dependencies, package 1 doesn't
+        }
+        // both have dependencies
+        if (in_array($p1name, $p2deps)) {
+            return -1; // put package 1 first
+        }
+        if (in_array($p2name, $p1deps)) {
+            return 1; // put package 2 first
+        }
+        // doesn't really matter if neither depends on the other
+        return 0;
+    }
+
+    // }}}
+    // {{{ _sortPkgDepsRev()
+
+    /**
+     * Compare two package's package.xml, and sort
+     * so that dependencies are uninstalled last
+     *
+     * This is a crude compare, real dependency checking is done on uninstall.
+     * The only purpose this serves is to make the command-line
+     * order-independent (you can list a dependency first, and
+     * uninstallation occurs in the order required)
+     * @access private
+     */
+    function _sortPkgDepsRev($p1, $p2)
+    {
+        $p1name = $p1['info']['package'];
+        $p2name = $p2['info']['package'];
+        $p1deps = PEAR_Common::_getRevPkgDeps($p1);
+        $p2deps = PEAR_Common::_getRevPkgDeps($p2);
+        if (!count($p1deps) && !count($p2deps)) {
+            return 0; // order makes no difference
+        }
+        if (!count($p1deps)) {
+            return 1; // package 2 has dependencies, package 1 doesn't
+        }
+        if (!count($p2deps)) {
+            return -1; // package 2 has dependencies, package 1 doesn't
+        }
+        // both have dependencies
+        if (in_array($p1name, $p2deps)) {
+            return 1; // put package 1 last
+        }
+        if (in_array($p2name, $p1deps)) {
+            return -1; // put package 2 last
+        }
+        // doesn't really matter if neither depends on the other
+        return 0;
+    }
+
+    // }}}
+    // {{{ _getPkgDeps()
+
+    /**
+     * get an array of package dependency names
+     * @access private
+     */
+    function _getPkgDeps($p)
+    {
+        if (!isset($p['info']['releases'])) {
+            return array();
+        }
+        $rel = array_shift($p['info']['releases']);
+        if (!isset($rel['deps'])) {
+            return array();
+        }
+        $ret = array();
+        foreach($rel['deps'] as $dep) {
+            if ($dep['type'] == 'pkg') {
+                $ret[] = $dep['name'];
+            }
+        }
+        return $ret;
+    }
+
+    // }}}
+    // {{{ _getRevPkgDeps()
+
+    /**
+     * get an array of package dependency names for uninstall
+     * @access private
+     */
+    function _getRevPkgDeps($p)
+    {
+        if (!isset($p['info']['release_deps'])) {
+            return array();
+        }
+        $ret = array();
+        foreach($p['info']['release_deps'] as $dep) {
+            if ($dep['type'] == 'pkg') {
+                $ret[] = $dep['name'];
+            }
+        }
+        return $ret;
+    }
+
     // }}}
 }
 
index 37afd71a368c88db3a2b4580aa9af30e4ba3add3..3aca0ebc7e181b4dabcf95eb22f020663f1f97b4 100644 (file)
@@ -26,6 +26,7 @@ define('PEAR_DEPENDENCY_CONFLICT',       -2);
 define('PEAR_DEPENDENCY_UPGRADE_MINOR',  -3);
 define('PEAR_DEPENDENCY_UPGRADE_MAJOR',  -4);
 define('PEAR_DEPENDENCY_BAD_DEPENDENCY', -5);
+define('PEAR_DEPENDENCY_MISSING_OPTIONAL', -6);
 
 /**
  * Dependency check for PEAR packages
@@ -54,13 +55,16 @@ class PEAR_Dependency
     * This method maps the XML dependency definition to the
     * corresponding one from PEAR_Dependency
     *
+    * <pre>
     * $opts => Array
     *    (
     *        [type] => pkg
     *        [rel] => ge
     *        [version] => 3.4
     *        [name] => HTML_Common
+    *        [optional] => false
     *    )
+    * </pre>
     *
     * @param  string Error message
     * @param  array  Options
@@ -71,13 +75,15 @@ class PEAR_Dependency
         $rel = isset($opts['rel']) ? $opts['rel'] : 'has';
         $req = isset($opts['version']) ? $opts['version'] : null;
         $name = isset($opts['name']) ? $opts['name'] : null;
+        $opt = (isset($opts['optional']) && $opts['optional'] == 'yes') ?
+            $opts['optional'] : null;
         $errmsg = '';
         switch ($opts['type']) {
             case 'pkg':
-                return $this->checkPackage($errmsg, $name, $req, $rel);
+                return $this->checkPackage($errmsg, $name, $req, $rel, $opt);
                 break;
             case 'ext':
-                return $this->checkExtension($errmsg, $name, $req, $rel);
+                return $this->checkExtension($errmsg, $name, $req, $rel, $opt);
                 break;
             case 'php':
                 return $this->checkPHP($errmsg, $req, $rel);
@@ -105,10 +111,12 @@ class PEAR_Dependency
      * @param string $name      Name of the package to test
      * @param string $version   The package version required
      * @param string $relation  How to compare versions with eachother
+     * @param bool   $opt       Whether the relationship is optional
      *
      * @return mixed bool false if no error or the error string
      */
-    function checkPackage(&$errmsg, $name, $req = null, $relation = 'has')
+    function checkPackage(&$errmsg, $name, $req = null, $relation = 'has',
+                          $opt = false)
     {
         if (substr($relation, 0, 2) == 'v.') {
             $relation = substr($relation, 2);
@@ -116,6 +124,10 @@ class PEAR_Dependency
         switch ($relation) {
             case 'has':
                 if (!$this->registry->packageExists($name)) {
+                    if ($opt) {
+                        $errmsg = "package `$name' is recommended to utilize some features.";
+                        return PEAR_DEPENDENCY_MISSING_OPTIONAL;
+                    }
                     $errmsg = "requires package `$name'";
                     return PEAR_DEPENDENCY_MISSING;
                 }
@@ -136,10 +148,14 @@ class PEAR_Dependency
                 if (!$this->registry->packageExists($name)
                     || !version_compare("$version", "$req", $relation))
                 {
+                    $code = $this->codeFromRelation($relation, $version, $req);
+                    if ($opt) {
+                        $errmsg = "package `$name' version $req is recommended to utilize some features.";
+                        return PEAR_DEPENDENCY_MISSING_OPTIONAL;
+                    }
                     $errmsg = "requires package `$name' " .
                         $this->signOperator($relation) . " $req";
-                    $code = $this->codeFromRelation($relation, $version, $req);
-                    return PEAR_DEPENDENCY_MISSING;
+                    return $code;
                 }
                 return false;
         }
@@ -151,11 +167,12 @@ class PEAR_Dependency
      * Check package dependencies on uninstall
      *
      * @param string $error     The resultant error string
+     * @param string $warning   The resultant warning string
      * @param string $name      Name of the package to test
      *
      * @return bool true if there were errors
      */
-    function checkPackageUninstall(&$error, $package)
+    function checkPackageUninstall(&$error, &$warning, $package)
     {
         $error = null;
         $packages = $this->registry->listPackages();
@@ -169,10 +186,14 @@ class PEAR_Dependency
             }
             foreach ($deps as $dep) {
                 if ($dep['type'] == 'pkg' && strcasecmp($dep['name'], $package) == 0) {
+                    if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+                        $warning .= "\nWarning: Package '$pkg' optionally depends on '$package'";
+                    } else {
                     $error .= "Package '$pkg' depends on '$package'\n";
                 }
             }
         }
+        }
         return ($error) ? true : false;
     }
 
@@ -182,13 +203,19 @@ class PEAR_Dependency
      * @param string $name        Name of the extension to test
      * @param string $req_ext_ver Required extension version to compare with
      * @param string $relation    How to compare versions with eachother
+     * @param bool   $opt       Whether the relationship is optional
      *
      * @return mixed bool false if no error or the error string
      */
-    function checkExtension(&$errmsg, $name, $req = null, $relation = 'has')
+    function checkExtension(&$errmsg, $name, $req = null, $relation = 'has',
+        $opt = false)
     {
         // XXX (ssb): could we avoid loading the extension here?
         if (!PEAR::loadExtension($name)) {
+            if ($opt) {
+                $errmsg = "'$name' PHP extension is recommended to utilize some features";
+                return PEAR_DEPENDENCY_MISSING_OPTIONAL;
+            }
             $errmsg = "'$name' PHP extension is not installed";
             return PEAR_DEPENDENCY_MISSING;
         }
@@ -202,9 +229,13 @@ class PEAR_Dependency
             // Force params to be strings, otherwise the comparation will fail (ex. 0.9==0.90)
             settype($req, "string");
             if (!version_compare("$ext_ver", "$req", $operator)) {
-                $retval = "'$name' PHP extension version " .
+                $errmsg = "'$name' PHP extension version " .
                     $this->signOperator($operator) . " $req is required";
                 $code = $this->codeFromRelation($relation, $ext_ver, $req);
+                if ($opt) {
+                    $errmsg = "'$name' PHP extension version $req is recommended to utilize some features";
+                    return PEAR_DEPENDENCY_MISSING_OPTIONAL;
+                }
             }
         }
         return $code;
index 81326d27b42651286c7952c74dcc75e84dbd20ac..a5cb8f26493adf31e577a362960b209c72097da2 100644 (file)
@@ -472,10 +472,13 @@ class PEAR_Frontend_CLI extends PEAR
     // {{{ log(text)
 
 
-    function log($text)
+    function log($text, $append_crlf = true)
     {
+        if ($append_crlf) {
         return $this->_displayLine($text);
     }
+        return $this->_display($text);
+    }
 
 
     // }}}
index 416fab9991b2cef3ae14c86fa34345ef0c0c7f6d..4592d88898658e948aa6f73bca06d98d319f7b7b 100644 (file)
@@ -125,7 +125,7 @@ class PEAR_Installer extends PEAR_Common
     // {{{ _deletePackageFiles()
 
     /**
-     * Delete a package's installed files, remove empty directories.
+     * Delete a package's installed files, does not remove empty directories.
      *
      * @param string $package package name
      *
@@ -155,7 +155,14 @@ class PEAR_Installer extends PEAR_Common
     // }}}
     // {{{ _installFile()
 
-    function _installFile($file, $atts, $tmp_path)
+    /**
+     * @param string filename
+     * @param array attributes from <file> tag in package.xml
+     * @param string path to install the file in
+     * @param array options from command-line
+     * @access private
+     */
+    function _installFile($file, $atts, $tmp_path, $options)
     {
         static $os;
         if (isset($atts['platform'])) {
@@ -192,6 +199,7 @@ class PEAR_Installer extends PEAR_Common
             default:
                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
         }
+        $save_destdir = $dest_dir;
         if (!empty($atts['baseinstalldir'])) {
             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
         }
@@ -222,6 +230,10 @@ class PEAR_Installer extends PEAR_Common
             $this->log(3, "+ mkdir $dest_dir");
         }
         if (empty($atts['replacements'])) {
+            if (!file_exists($orig_file)) {
+                return $this->raiseError("file does not exist",
+                                         PEAR_INSTALLER_FAILED);
+            }
             if (!@copy($orig_file, $dest_file)) {
                 return $this->raiseError("failed to write $dest_file",
                                          PEAR_INSTALLER_FAILED);
@@ -231,6 +243,10 @@ class PEAR_Installer extends PEAR_Common
                 $md5sum = md5_file($dest_file);
             }
         } else {
+            if (!file_exists($orig_file)) {
+                return $this->raiseError("file does not exist",
+                                         PEAR_INSTALLER_FAILED);
+            }
             $fp = fopen($orig_file, "r");
             $contents = fread($fp, filesize($orig_file));
             fclose($fp);
@@ -249,10 +265,19 @@ class PEAR_Installer extends PEAR_Common
                     }
                 } elseif ($a['type'] == 'pear-config') {
                     $to = $this->config->get($a['to']);
+                    if (is_null($to)) {
+                        $this->log(0, "invalid pear-config replacement: $a[to]");
+                        continue;
+                    }
                 } elseif ($a['type'] == 'package-info') {
+                    if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) {
                     $to = $this->pkginfo[$a['to']];
+                    } else {
+                        $this->log(0, "invalid package-info replacement: $a[to]");
+                        continue;
+                    }
                 }
-                if ($to) {
+                if (!is_null($to)) {
                     $subst_from[] = $a['from'];
                     $subst_to[] = $to;
                 }
@@ -273,12 +298,19 @@ class PEAR_Installer extends PEAR_Common
             fclose($wp);
         }
         if (isset($md5sum)) {
-            if ($md5sum == $atts['md5sum']) {
-                $this->log(3, "md5sum ok: $final_dest_file");
+            if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
+                $this->log(2, "md5sum ok: $final_dest_file");
+            } else {
+                if (empty($options['force'])) {
+                    // delete the file
+                    @unlink($dest_file);
+                    return $this->raiseError("bad md5sum for file $final_dest_file",
+                                             PEAR_INSTALLER_FAILED);
             } else {
                 $this->log(0, "warning : bad md5sum for file $final_dest_file");
             }
         }
+        }
         if (!OS_WINDOWS) {
             if ($atts['role'] == 'script') {
                 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
@@ -292,10 +324,9 @@ class PEAR_Installer extends PEAR_Common
             }
         }
         $this->addFileOperation("rename", array($dest_file, $final_dest_file));
-
-        // XXX SHOULD BE DONE ONLY AFTER COMMIT
         // Store the full path where the file was installed for easy unistall
-        $this->pkginfo['filelist'][$file]['installed_as'] = $installed_as;
+        $this->addFileOperation("installed_as", array($file, $installed_as,
+                                $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
 
         //$this->log(2, "installed: $dest_file");
         return PEAR_INSTALLER_OK;
@@ -304,8 +335,34 @@ class PEAR_Installer extends PEAR_Common
     // }}}
     // {{{ addFileOperation()
 
+    /**
+     * Add a file operation to the current file transaction.
+     *
+     * @see startFileTransaction()
+     * @var string $type This can be one of:
+     *    - rename:  rename a file ($data has 2 values)
+     *    - chmod:   change permissions on a file ($data has 2 values)
+     *    - delete:  delete a file ($data has 1 value)
+     *    - rmdir:   delete a directory if empty ($data has 1 value)
+     *    - installed_as: mark a file as installed ($data has 4 values).
+     * @var array $data For all file operations, this array must contain the
+     *    full path to the file or directory that is being operated on.  For
+     *    the rename command, the first parameter must be the file to rename,
+     *    the second its new name.
+     *
+     *    The installed_as operation contains 4 elements in this order:
+     *    1. Filename as listed in the filelist element from package.xml
+     *    2. Full path to the installed file
+     *    3. Full path from the php_dir configuration variable used in this
+     *       installation
+     *    4. Relative path from the php_dir that this file is installed in
+     */
     function addFileOperation($type, $data)
     {
+        if (!is_array($data)) {
+            return $this->raiseError('Internal Error: $data in addFileOperation'
+                . ' must be an array, was ' . gettype($data));
+        }
         if ($type == 'chmod') {
             $octmode = decoct($data[0]);
             $this->log(3, "adding to transaction: $type $octmode $data[1]");
@@ -339,6 +396,9 @@ class PEAR_Installer extends PEAR_Common
             list($type, $data) = $tr;
             switch ($type) {
                 case 'rename':
+                    if (!file_exists($data[0])) {
+                        $errors[] = "cannot rename file $data[0], doesn't exist";
+                    }
                     // check that dest dir. is writable
                     if (!is_writable(dirname($data[1]))) {
                         $errors[] = "permission denied ($type): $data[1]";
@@ -347,10 +407,13 @@ class PEAR_Installer extends PEAR_Common
                 case 'chmod':
                     // check that file is writable
                     if (!is_writable($data[1])) {
-                        $errors[] = "permission denied ($type): $data[1]";
+                        $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
                     }
                     break;
                 case 'delete':
+                    if (!file_exists($data[0])) {
+                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
+                    }
                     // check that directory is writable
                     if (file_exists($data[0]) && !is_writable(dirname($data[0]))) {
                         $errors[] = "permission denied ($type): $data[0]";
@@ -371,11 +434,12 @@ class PEAR_Installer extends PEAR_Common
             list($type, $data) = $tr;
             switch ($type) {
                 case 'rename':
+                    @unlink($data[1]);
                     @rename($data[0], $data[1]);
                     $this->log(3, "+ mv $data[0] $data[1]");
                     break;
                 case 'chmod':
-                    @chmod($data[0], $data[1]);
+                    @chmod($data[1], $data[0]);
                     $octmode = decoct($data[0]);
                     $this->log(3, "+ chmod $octmode $data[1]");
                     break;
@@ -387,9 +451,21 @@ class PEAR_Installer extends PEAR_Common
                     @rmdir($data[0]);
                     $this->log(3, "+ rmdir $data[0]");
                     break;
+                case 'installed_as':
+                    $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1];
+                    if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
+                        $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true;
+                        while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
+                              && $data[3] != '.') {
+                            $this->pkginfo['filelist']['dirtree']
+                                [$this->_prependPath($data[3], $data[2])] = true;
+                            $data[3] = dirname($data[3]);
+                        }
+                    }
+                    break;
             }
         }
-        $this->log(2, "successfully commited $n file operations");
+        $this->log(2, "successfully committed $n file operations");
         $this->file_operations = array();
         return true;
     }
@@ -416,6 +492,21 @@ class PEAR_Installer extends PEAR_Common
                     break;
                 case 'delete':
                     break;
+                case 'installed_as':
+                    unset($this->pkginfo['filelist'][$data[0]]['installed_as']);
+                    if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
+                        unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]);
+                        while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
+                              && $data[3] != '.') {
+                            unset($this->pkginfo['filelist']['dirtree']
+                                [$this->_prependPath($data[3], $data[2])]);
+                            $data[3] = dirname($data[3]);
+                        }
+                    }
+                    if (!count($this->pkginfo['filelist']['dirtree'])) {
+                        unset($this->pkginfo['filelist']['dirtree']);
+                    }
+                    break;
             }
         }
         $this->file_operations = array();
@@ -424,8 +515,11 @@ class PEAR_Installer extends PEAR_Common
     // }}}
     // {{{ getPackageDownloadUrl()
 
-    function getPackageDownloadUrl($package)
+    function getPackageDownloadUrl($package, $version = null)
     {
+        if ($version) {
+            $package .= "-$version";
+        }
         if ($this === null || $this->config === null) {
             $package = "http://pear.php.net/get/$package";
         } else {
@@ -464,75 +558,334 @@ class PEAR_Installer extends PEAR_Common
     }
 
     // }}}
+    // {{ extractDownloadFileName($pkgfile, &$version)
 
-    // {{{ install()
+    function extractDownloadFileName($pkgfile, &$version)
+    {
+        if (@is_file($pkgfile)) {
+            return $pkgfile;
+        }
+        // regex defined in Common.php
+        if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) {
+            $version = (isset($m[3])) ? $m[3] : null;
+            return $m[1];
+        }
+        $version = null;
+        return $pkgfile;
+    }
 
+    // }}}
+    // {{{ _downloadFile()
     /**
-     * Installs the files within the package file specified.
-     *
-     * @param $pkgfile path to the package file
-     *
-     * @return array package info if successful, null if not
+     * @param string filename to download
+     * @param PEAR_Config Configuration object
+     * @param array options returned from Console_GetOpt
+     * @param array empty array to populate with error messages, if any
+     * @param string version/state
+     * @param string original value passed to command-line
+     * @param string preferred state (snapshot/devel/alpha/beta/stable)
+     * @access private
      */
-
-    function install($pkgfile, $options = array())
+    function _downloadFile($pkgfile, &$config, $options, &$errors, $version,
+                           $origpkgfile, $state)
     {
-        // recognized options:
-        // - force         : force installation
-        // - register-only : update registry but don't install files
-        // - upgrade       : upgrade existing install
-        // - soft          : fail silently
-        //
-        $php_dir = $this->config->get('php_dir');
-        if (isset($options['installroot'])) {
-            if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
-                $options['installroot'] = substr($options['installroot'], 0, -1);
-            }
-            $php_dir = $this->_prependPath($php_dir, $options['installroot']);
-            $this->installroot = $options['installroot'];
-        } else {
-            $this->installroot = '';
-        }
-        $this->registry = &new PEAR_Registry($php_dir);
         $need_download = false;
-        //  ==> XXX should be removed later on
-        $flag_old_format = false;
         if (preg_match('#^(http|ftp)://#', $pkgfile)) {
             $need_download = true;
         } elseif (!@is_file($pkgfile)) {
             if ($this->validPackageName($pkgfile)) {
-                if ($this->registry->packageExists($pkgfile) &&
-                    empty($options['upgrade']) && empty($options['force']))
-                {
-                    return $this->raiseError("$pkgfile already installed");
+                if ($this->registry->packageExists($pkgfile)) {
+                    if (empty($options['upgrade']) && empty($options['force'])) {
+                        $errors[] = "$pkgfile already installed";
+                        return;
+                    }
                 }
-                $pkgfile = $this->getPackageDownloadUrl($pkgfile);
+                $pkgfile = $this->getPackageDownloadUrl($pkgfile, $version);
                 $need_download = true;
             } else {
                 if (strlen($pkgfile)) {
-                    return $this->raiseError("Could not open the package file: $pkgfile");
+                    $errors[] = "Could not open the package file: $pkgfile";
                 } else {
-                    return $this->raiseError("No package file given");
+                    $errors[] = "No package file given";
                 }
+                return;
             }
         }
 
         // Download package -----------------------------------------------
         if ($need_download) {
-            $downloaddir = $this->config->get('download_dir');
+            $downloaddir = $config->get('download_dir');
             if (empty($downloaddir)) {
                 if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
                     return $downloaddir;
                 }
-                $this->log(2, '+ tmp dir created at ' . $downloaddir);
+                $this->log(3, '+ tmp dir created at ' . $downloaddir);
             }
             $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
+            $this->pushErrorHandling(PEAR_ERROR_RETURN);
             $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
+            $this->popErrorHandling();
             if (PEAR::isError($file)) {
+                if ($this->validPackageName($origpkgfile)) {
+                    include_once 'PEAR/Remote.php';
+                    $remote = new PEAR_Remote($config);
+                    if (!PEAR::isError($info = $remote->call('package.info',
+                          $origpkgfile))) {
+                        if (!count($info['releases'])) {
+                            return $this->raiseError('Package ' . $origpkgfile .
+                            ' has no releases');
+                        } else {
+                            return $this->raiseError('No releases of preferred state "'
+                            . $state . '" exist for package ' . $origpkgfile .
+                            '.  Use ' . $origpkgfile . '-state to install another' .
+                            ' state (like ' . $origpkgfile .'-beta)');
+                        }
+                    } else {
+                        return $pkgfile;
+                    }
+                } else {
                 return $this->raiseError($file);
             }
+            }
             $pkgfile = $file;
         }
+        return $pkgfile;
+    }
+
+    // }}}
+    // {{{ download()
+
+    /**
+     * Download any files and their dependencies, if necessary
+     *
+     * @param array a mixed list of package names, local files, or package.xml
+     * @param PEAR_Config
+     * @param array options from the command line
+     * @param array this is the array that will be populated with packages to
+     *              install.  Format of each entry:
+     *
+     * <code>
+     * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
+     *    'info' => array() // parsed package.xml
+     * );
+     * </code>
+     * @param array this will be populated with any error messages
+     * @param false private recursion variable
+     * @param false private recursion variable
+     * @param false private recursion variable
+     */
+    function download($packages, $options, &$config, &$installpackages,
+                      &$errors, $installed = false, $willinstall = false, $state = false)
+    {
+        // recognized options:
+        // - onlyreqdeps   : install all required dependencies as well
+        // - alldeps       : install all dependencies, including optional
+        //
+        if (!$willinstall) {
+            $willinstall = array();
+        }
+        if (!$state) {
+            $state = $config->get('preferred_state');
+            if (!$state) {
+                // don't inadvertantly use a non-set preferred_state
+                $state = null;
+            }
+        }
+        $mywillinstall = array();
+        $php_dir = $config->get('php_dir');
+        if (isset($options['installroot'])) {
+            if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
+                $options['installroot'] = substr($options['installroot'], 0, -1);
+            }
+            $php_dir = $this->_prependPath($php_dir, $options['installroot']);
+            $this->installroot = $options['installroot'];
+        } else {
+            $this->installroot = '';
+        }
+        $this->registry = &new PEAR_Registry($php_dir);
+
+        // download files in this list if necessary
+        foreach($packages as $pkgfile) {
+            if (!is_file($pkgfile)) {
+                $origpkgfile = $pkgfile;
+                $pkgfile = $this->extractDownloadFileName($pkgfile, $version);
+                if ($version === null) {
+                    // use preferred state if no version number was specified
+                    $version = $state;
+                }
+                if ($this->validPackageName($pkgfile) && !isset($options['upgrade'])) {
+                    if ($this->registry->packageExists($pkgfile)) {
+                        $this->log(0, "Package '$pkgfile' already installed, skipping");
+                        // ignore dependencies that are installed unless we are upgrading
+                        continue;
+                    }
+                }
+                $pkgfile = $this->_downloadFile($pkgfile, $config, $options, $errors,
+                                                $version, $origpkgfile, $state);
+                if (PEAR::isError($pkgfile)) {
+                    return $pkgfile;
+                }
+            }
+            $tempinfo = $this->infoFromAny($pkgfile);
+            if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) {
+                // ignore dependencies if there are any errors
+                if (!PEAR::isError($tempinfo)) {
+                    $mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps'];
+                }
+            }
+            $installpackages[] = array('pkg' => $tempinfo['package'],
+                                       'file' => $pkgfile, 'info' => $tempinfo);
+        }
+
+        // extract dependencies from downloaded files and then download them
+        // if necessary
+        if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) {
+            include_once "PEAR/Remote.php";
+            $remote = new PEAR_Remote($config);
+            if (!$installed) {
+                $installed = $this->registry->listPackages();
+                array_walk($installed, create_function('&$v,$k','$v = strtolower($v);'));
+                $installed = array_flip($installed);
+            }
+            $deppackages = array();
+            // construct the list of dependencies for each file
+            foreach ($mywillinstall as $package => $alldeps) {
+                if (!is_array($alldeps)) {
+                    continue;
+                }
+                foreach($alldeps as $info) {
+                    if ($info['type'] != 'pkg') {
+                        continue;
+                    }
+                    if (!isset($options['alldeps']) && isset($info['optional']) &&
+                          $info['optional'] == 'yes') {
+                        // skip optional deps
+                        $this->log(0, "skipping Package $package optional dependency $info[name]");
+                        continue;
+                    }
+                    // get releases
+                    $releases = $remote->call('package.info', $info['name'], 'releases');
+                    if (PEAR::isError($releases)) {
+                        return $releases;
+                    }
+                    if (!count($releases)) {
+                        if (!isset($installed[strtolower($info['name'])])) {
+                            $errors[] = "Package $package dependency $info[name] ".
+                                "has no releases";
+                        }
+                        continue;
+                    }
+                    $found = false;
+                    $save = $releases;
+                    while(count($releases) && !$found) {
+                        if (!empty($state) && $state != 'any') {
+                            list($release_version,$release) = each($releases);
+                            if ($state != $release['state'] &&
+                                  !in_array($release['state'],
+                                    $this->betterStates($state))) {
+                                // drop this release - it ain't stable enough
+                                array_shift($releases);
+                            } else {
+                                $found = true;
+                            }
+                        } else {
+                            $found = true;
+                        }
+                    }
+                    if (!count($releases) && !$found) {
+                        $get = array();
+                        foreach($save as $release) {
+                            $get = array_merge($get,
+                                $this->betterStates($release['state'], true));
+                        }
+                        $savestate = array_shift($get);
+                        $errors[] = "Release for $package dependency $info[name] " .
+                            "has state '$savestate', requires $state";
+                        continue;
+                    }
+                    if (in_array(strtolower($info['name']), $willinstall) ||
+                          isset($mywillinstall[strtolower($info['name'])])) {
+                        // skip upgrade check for packages we will install
+                        continue;
+                    }
+                    if (!isset($installed[strtolower($info['name'])])) {
+                        // skip upgrade check for packages we don't have installed
+                        $deppackages[] = $info['name'];
+                        continue;
+                    }
+
+                    // see if a dependency must be upgraded
+                    $inst_version = $this->registry->packageInfo($info['name'], 'version');
+                    if (!isset($info['version'])) {
+                        // this is a rel='has' dependency, check against latest
+                        if (version_compare($release_version, $inst_version, 'le')) {
+                            continue;
+                        } else {
+                            $deppackages[] = $info['name'];
+                            continue;
+                        }
+                    }
+                    if (version_compare($info['version'], $inst_version, 'le')) {
+                        // installed version is up-to-date
+                        continue;
+                    }
+                    $deppackages[] = $info['name'];
+                } // foreach($alldeps
+            } // foreach($willinstall
+
+            if (count($deppackages)) {
+                // check dependencies' dependencies
+                // combine the list of packages to install
+                $temppack = array();
+                foreach($installpackages as $p) {
+                    $temppack[] = strtolower($p['info']['package']);
+                }
+                foreach($deppackages as $pack) {
+                    $temppack[] = strtolower($pack);
+                }
+                $willinstall = array_merge($willinstall, $temppack);
+                $this->download($deppackages, $options, $config, $installpackages,
+                    $errors, $installed, $willinstall, $state);
+            }
+        } // if --alldeps or --onlyreqdeps
+    }
+
+    // }}}
+    // {{{ install()
+
+    /**
+     * Installs the files within the package file specified.
+     *
+     * @param string $pkgfile path to the package file
+     * @param array $options
+     * recognized options:
+     * - installroot   : optional prefix directory for installation
+     * - force         : force installation
+     * - register-only : update registry but don't install files
+     * - upgrade       : upgrade existing install
+     * - soft          : fail silently
+     * - nodeps        : ignore dependency conflicts/missing dependencies
+     * - alldeps       : install all dependencies
+     * - onlyreqdeps   : install only required dependencies
+     *
+     * @return array package info if successful, null if not
+     */
+
+    function install($pkgfile, $options = array())
+    {
+        $php_dir = $this->config->get('php_dir');
+        if (isset($options['installroot'])) {
+            if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
+                $options['installroot'] = substr($options['installroot'], 0, -1);
+            }
+            $php_dir = $this->_prependPath($php_dir, $options['installroot']);
+            $this->installroot = $options['installroot'];
+        } else {
+            $this->installroot = '';
+        }
+        $this->registry = &new PEAR_Registry($php_dir);
+        //  ==> XXX should be removed later on
+        $flag_old_format = false;
 
         if (substr($pkgfile, -4) == '.xml') {
             $descfile = $pkgfile;
@@ -549,7 +902,7 @@ class PEAR_Installer extends PEAR_Common
             if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
                 return $tmpdir;
             }
-            $this->log(2, '+ tmp dir created at ' . $tmpdir);
+            $this->log(3, '+ tmp dir created at ' . $tmpdir);
 
             $tar = new Archive_Tar($pkgfile);
             if (!@$tar->extract($tmpdir)) {
@@ -637,6 +990,8 @@ class PEAR_Installer extends PEAR_Common
             }
         }
 
+        $this->startFileTransaction();
+
         if (empty($options['upgrade'])) {
             // checks to do only when installing new packages
             if (empty($options['force']) && $this->registry->packageExists($pkgname)) {
@@ -644,9 +999,10 @@ class PEAR_Installer extends PEAR_Common
             }
         } else {
             // checks to do only when upgrading packages
-            if (!$this->registry->packageExists($pkgname)) {
+/*            if (!$this->registry->packageExists($pkgname)) {
                 return $this->raiseError("$pkgname not installed");
-            }
+            }*/
+            if ($this->registry->packageExists($pkgname)) {
             $v1 = $this->registry->packageInfo($pkgname, 'version');
             $v2 = $pkginfo['version'];
             $cmp = version_compare("$v1", "$v2", 'gt');
@@ -660,6 +1016,7 @@ class PEAR_Installer extends PEAR_Common
                 }
             }
         }
+        }
 
         // Copy files to dest dir ---------------------------------------
 
@@ -690,11 +1047,14 @@ class PEAR_Installer extends PEAR_Common
 
             foreach ($pkginfo['filelist'] as $file => $atts) {
                 $this->expectError(PEAR_INSTALLER_FAILED);
-                $res = $this->_installFile($file, $atts, $tmp_path);
+                $res = $this->_installFile($file, $atts, $tmp_path, $options);
                 $this->popExpect();
                 if (PEAR::isError($res)) {
                     if (empty($options['ignore-errors'])) {
                         $this->rollbackFileTransaction();
+                        if ($res->getMessage() == "file does not exist") {
+                            $this->raiseError("file $file in package.xml does not exist");
+                        }
                         return $this->raiseError($res);
                     } else {
                         $this->log(0, "Warning: " . $res->getMessage());
@@ -750,8 +1110,13 @@ class PEAR_Installer extends PEAR_Common
             }
             $ret = $this->registry->addPackage($pkgname, $pkginfo);
         } else {
+            // new: upgrade installs a package if it isn't installed
+            if (!$this->registry->packageExists($pkgname)) {
+                $ret = $this->registry->addPackage($pkgname, $pkginfo);
+            } else {
             $ret = $this->registry->updatePackage($pkgname, $pkginfo, false);
         }
+        }
         if (!$ret) {
             return null;
         }
@@ -761,6 +1126,16 @@ class PEAR_Installer extends PEAR_Common
     // }}}
     // {{{ uninstall()
 
+    /**
+     * Uninstall a package
+     *
+     * This method removes all files installed by the application, and then
+     * removes any empty directories.
+     * @param string package name
+     * @param array Command-line options.  Possibilities include:
+     *
+     *              - installroot: base installation dir, if not the default
+     */
     function uninstall($package, $options = array())
     {
         $php_dir = $this->config->get('php_dir');
@@ -774,13 +1149,21 @@ class PEAR_Installer extends PEAR_Common
             $this->installroot = '';
         }
         $this->registry = &new PEAR_Registry($php_dir);
+        $filelist = $this->registry->packageInfo($package, 'filelist');
+        if ($filelist == null) {
+            return $this->raiseError("$package not installed");
+        }
         if (empty($options['nodeps'])) {
             $depchecker = &new PEAR_Dependency($this->registry);
-            $error = $depchecker->checkPackageUninstall($errors, $package);
+            $error = $depchecker->checkPackageUninstall($errors, $warning, $package);
             if ($error) {
                 return $this->raiseError($errors . 'uninstall failed');
             }
+            if ($warning) {
+                $this->log(0, $warning);
+            }
         }
+        $this->startFileTransaction();
         // Delete the files
         if (PEAR::isError($err = $this->_deletePackageFiles($package))) {
             $this->rollbackFileTransaction();
@@ -789,12 +1172,34 @@ class PEAR_Installer extends PEAR_Common
         if (!$this->commitFileTransaction()) {
             $this->rollbackFileTransaction();
             return $this->raiseError("uninstall failed");
+        } else {
+            $this->startFileTransaction();
+            if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) {
+                return $this->registry->deletePackage($package);
+            }
+            // attempt to delete empty directories
+            uksort($filelist['dirtree'], array($this, '_sortDirs'));
+            foreach($filelist['dirtree'] as $dir => $notused) {
+                $this->addFileOperation('rmdir', array($dir));
+            }
+            if (!$this->commitFileTransaction()) {
+                $this->rollbackFileTransaction();
+            }
         }
 
         // Register that the package is no longer installed
         return $this->registry->deletePackage($package);
     }
 
+    // }}}
+    // {{{ _sortDirs()
+    function _sortDirs($a, $b)
+    {
+        if (strnatcmp($a, $b) == -1) return 1;
+        if (strnatcmp($a, $b) == 1) return -1;
+        return 0;
+    }
+
     // }}}
     // {{{ checkDeps()
 
@@ -818,10 +1223,11 @@ class PEAR_Installer extends PEAR_Common
                 $code = $depchecker->callCheckMethod($error, $dep);
                 if ($code) {
                     if (isset($dep['optional']) && $dep['optional'] == 'yes') {
+/* die ugly hack die
                         // Ugly hack to adjust the error messages
                         $error = str_replace('requires ', '', $error);
                         $error = ucfirst($error);
-                        $error = $error . ' is recommended to utilize some features.';
+                        $error = $error . ' is recommended to utilize some features.';*/
                         $optional_deps[] = array($dep, $code, $error);
                     } else {
                         $failed_deps[] = array($dep, $code, $error);
@@ -893,6 +1299,19 @@ class PEAR_Installer extends PEAR_Common
             case 'done':
                 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
                 break;
+            case 'bytesread':
+                static $bytes;
+                if (empty($bytes)) {
+                    $bytes = 0;
+                }
+                if (!($bytes % 10240)) {
+                    $this->log(1, '.', false);
+                }
+                $bytes += $params;
+                break;
+            case 'start':
+                $this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
+                break;
         }
         if (method_exists($this->ui, '_downloadCallback'))
             $this->ui->_downloadCallback($msg, $params);
index 96ac23087f6dfe52a815b03b1d3bbd92440f85e9..c28d2039aea58320d8cfe4e972d5f6784562141a 100644 (file)
@@ -276,7 +276,7 @@ class PEAR_Registry extends PEAR
             }
             $open_mode = 'w';
             // XXX People reported problems with LOCK_SH and 'w'
-            if ($mode === LOCK_SH) {
+            if ($mode === LOCK_SH || $mode === LOCK_UN) {
                 if (@!is_file($this->lockfile)) {
                     touch($this->lockfile);
                 }
index 8f5e5fae51f1f64cbbd464a9134a46386a202847..5915b6b5d214176e63d38d48a573de1a3f9e2c93 100644 (file)
@@ -41,6 +41,11 @@ $GLOBALS['_System_temp_files'] = array();
 *    print "could not delete file1 or dir1";
 * }
 *
+* In case you need to to pass file names with spaces,
+* pass the params as an array:
+*
+* System::rm(array('-r', $file1, $dir1));
+*
 * @package  System
 * @author   Tomas V.V.Cox <cox@idecnet.com>
 * @version  $Revision$
@@ -123,17 +128,17 @@ class System
         }
         closedir($dir);
         sort($list);
+        if ($aktinst < $maxinst || $maxinst == 0) {
         foreach($list as $val) {
             $path = $sPath . DIRECTORY_SEPARATOR . $val;
             if (is_dir($path)) {
-                if ($aktinst < $maxinst || $maxinst == 0) {
                     $tmp = System::_dirToStruct($path, $maxinst, $aktinst+1);
                     $struct = array_merge_recursive($tmp, $struct);
-                }
             } else {
                 $struct['files'][] = $path;
             }
         }
+        }
         return $struct;
     }
 
@@ -445,5 +450,82 @@ class System
         }
         return $fallback;
     }
+
+    /**
+    * The "find" command
+    *
+    * Usage:
+    *
+    * System::find($dir);
+    * System::find("$dir -type d");
+    * System::find("$dir -type f");
+    * System::find("$dir -name *.php");
+    * System::find("$dir -name *.php -name *.htm*");
+    * System::find("$dir -maxdepth 1");
+    *
+    * Params implmented:
+    * $dir            -> Start the search at this directory
+    * -type d         -> return only directories
+    * -type f         -> return only files
+    * -maxdepth <n>   -> max depth of recursion
+    * -name <pattern> -> search pattern (bash style). Multiple -name param allowed
+    *
+    * @param  mixed Either array or string with the command line
+    * @return array Array of found files
+    *
+    */
+    function find($args)
+    {
+        if (!is_array($args)) {
+            $args = preg_split('/\s+/', $args, -1, PREG_SPLIT_NO_EMPTY);
+        }
+        $dir = array_shift($args);
+        $patterns = array();
+        $depth = 0;
+        $do_files = $do_dirs = true;
+        for ($i = 0; $i < count($args); $i++) {
+            switch ($args[$i]) {
+                case '-type':
+                    if (in_array($args[$i+1], array('d', 'f'))) {
+                        if ($args[$i+1] == 'd') {
+                             $do_files = false;
+                        } else {
+                            $do_dirs = false;
+                        }
+                    }
+                    $i++;
+                    break;
+                case '-name':
+                    $patterns[] = "(" . preg_replace(array('/\./', '/\*/'),
+                                                     array('\.', '.*'),
+                                                     $args[$i+1])
+                                      . ")";
+                    $i++;
+                    break;
+                case '-maxdepth':
+                    $depth = $args[$i+1];
+                    break;
+            }
+        }
+        $path = System::_dirToStruct($dir, $depth);
+        if ($do_files && $do_dirs) {
+            $files = array_merge($path['files'], $path['dirs']);
+        } elseif ($do_dirs) {
+            $files = $path['dirs'];
+        } else {
+            $files = $path['files'];
+        }
+        if (count($patterns)) {
+            $patterns = implode('|', $patterns);
+            $ret = array();
+            for ($i = 0; $i < count($files); $i++) {
+                if (preg_match("#^$patterns\$#", $files[$i])) {
+                    $ret[] = $files[$i];
+                }
+            }
+            return $ret;
+        }
+        return $files;
+    }
 }
 ?>
index f6b2563bc2ab530c9b20c279e868ae2f0836e3be..7bf4fb4a9c67c7866536c123dfc6f03108c09d51 100644 (file)
@@ -19,7 +19,7 @@
     </maintainer>
     <maintainer>
       <user>cox</user>
-      <role>developer</role>
+      <role>lead</role>
       <name>Tomas V.V.Cox</name>
       <email>cox@idecnet.com</email>
     </maintainer>
       <name>Pierre-Alain Joye</name>
       <email>pajoye@pearfr.org</email>
     </maintainer>
+    <maintainer>
+      <user>cellog</user>
+      <role>developer</role>
+      <name>Greg Beaver</name>
+      <email>cellog@php.net</email>
+    </maintainer>
   </maintainers>
   <release>
-    <version>1.2.1</version>
-    <state>stable</state>
-    <date>2003-08-15</date>
+    <version>1.3b1</version>
+    <date>2003-09-29</date>
+    <state>beta</state>
     <notes>
-- Set back the default library path (BC issues)
-    </notes>
+PEAR Base Class:
+
+* Fixed static calls to PEAR error-handling methods in classes
+* Added ability to use a static method callback for error-handling,
+  and removed use of inadvisable @ in setErrorHandling
+
+PEAR Installer:
+
+* Fixed #25117 - MD5 checksum should be case-insensitive
+* Added dependency on XML_RPC, and optional dependency on xmlrpc extension
+* Added --alldeps and --onlyreqdeps options to pear install/pear upgrade
+* Sorting of installation/uninstallation so package order on the command-line is
+  insignificant (fixes upgrade-all if every package is installed)
+* pear upgrade will now install if the package is not installed (necessary for
+  pear upgrade --alldeps, as installation is often necessary for new
+  dependencies)
+* fixed pear.bat if PHP is installed in a path like C:\Program Files\php
+* Added ability to specify "pear install package-version" or
+  "pear install package-state". For example: "pear install DB-1.2",
+  or "pear install DB-stable"
+* Fix #25008 - unhelpful error message
+* Fixed optional dependencies in Dependency.php
+* Fix #25322 - bad md5sum should be fatal error
+* Package uninstall now also removes empty directories
+* Fixed locking problems for reading commands (pear list, pear info)
+
+OS_Guess Class:
+
+* Fixed #25131 - OS_Guess warnings on empty lines from
+  popen(&quot;/usr/bin/cpp $tmpfile&quot;, &quot;r&quot;);
+
+System Class:
+
+* Fixed recursion deep param in _dirToStruct()
+* Added the System::find() command (read API doc for more info)
 
+    </notes>
     <provides type="class" name="OS_Guess" />
     <provides type="class" name="System" />
     <provides type="function" name="md5_file" />
       <dep type="php" rel="ge" version="4.1"/>
       <dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
+      <dep type="pkg" rel="ge" version="1.0.4">XML_RPC</dep>
+      <dep type="ext" rel="has" optional="yes">xmlrpc</dep>
     </deps>
   </release>
-  <changelog>
-    <release>
-      <version>1.2</version>
-      <state>stable</state>
-      <date>2003-08-14</date>
-      <notes>
-Changes from 1.1:
-
-* Changed license from PHP 2.02 to 3.0
-* Added support for optional dependencies
-* Made upgrade and uninstall package case insensitive
-* pear makerpm, now works and generates a better system independant spec file
-* pear install|build pecl-package, now exposes the compilation progress
-* Installer now checks dependencies on package uninstall
-* Added proxy support for remote commands using the xmlrcp C ext (Adam Ashley)
-* Added the command "download-all" (Alex Merz)
-* Made package dependency checking back to work
-* Added support for spaces in path names (Greg)
-* Various bugfixes
-* Added new pear "bundle" command, which downloads and uncompress a PECL package.
-The main purpouse of this command is for easily adding extensions to the PHP sources
-before compiling it.
-      </notes>
-    </release>
-    <release>
-        <version>1.1</version>
-        <state>stable</state>
-        <date>2003-01-10</date>
-        <notes>
-PEAR BASE CLASS:
-
-* PEAR_Error now supports exceptions when using Zend Engine 2.  Set the
-  error mode to PEAR_ERROR_EXCEPTION to make PEAR_Error throw itself
-  as an exception (invoke PEAR errors with raiseError() or throwError()
-  just like before).
-
-PEAR INSTALLER:
-
-* Packaging and validation now parses PHP source code (unless
-  ext/tokenizer is disabled) and does some coding standard conformance
-  checks.  Specifically, the names of classes and functions are
-  checked to ensure that they are prefixed with the package name.  If
-  your package has symbols that should be without this prefix, you can
-  override this warning by explicitly adding a "provides" entry in
-  your package.xml file.  See the package.xml file for this release
-  for an example (OS_Guess, System and md5_file).
-
-  All classes and non-private (not underscore-prefixed) methods and
-  functions are now registered during "pear package".
-        </notes>
-    </release>
-   <release>
-     <version>1.0.1</version>
-     <state>stable</state>
-     <date>2003-01-10</date>
-     <notes>
- * PEAR_Error class has call backtrace available by
-   calling getBacktrace().  Available if used with
-   PHP 4.3 or newer.
-
- * PEAR_Config class uses getenv() rather than $_ENV
-   to read environment variables.
-
- * System::which() Windows fix, now looks for
-   exe/bat/cmd/com suffixes rather than just exe
-
- * Added "pear cvsdiff" command
-
- * Windows output buffering bugfix for "pear" command
-
- * Multiple drives installation now works on windows
-
- * pear.bat uses ENV variables, allowing the installation
-   of many PEAR (windows)
- </notes>
-     <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-     </deps>
-   </release>
-   <release>
-     <version>1.0</version>
-     <state>stable</state>
-     <date>2002-12-27</date>
-     <notes>
- * set default cache_ttl to 1 hour
- * added "clear-cache" command
- </notes>
-     <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-     </deps>
-   </release>
-   <release>
-     <version>1.0b3</version>
-     <state>stable</state>
-     <date>2002-12-13</date>
-     <notes>
- * fixed "info" shortcut (conflicted with "install")
- * added "php_bin" config parameter
- * all "non-personal" config parameters now use
-   environment variables for defaults (very useful
-   to override the default php_dir on Windows!)
- </notes>
-     <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-     </deps>
-   </release>
-   <release>
-     <version>1.0b2</version>
-     <state>stable</state>
-     <date>2002-11-26</date>
-     <notes>
-Changes, Installer:
-* --force option no longer ignores errors, use
-  --ignore-errors instead
-* installer transactions: failed installs abort
-  cleanly, without leaving half-installed packages
-  around
-</notes>
-   </release>
-   <release>
-     <version>1.0b1</version>
-     <state>stable</state>
-     <date>2002-10-12</date>
-     <notes>
-New Features, Installer:
-* new command: "pear makerpm"
-* new command: "pear search"
-* new command: "pear upgrade-all"
-* new command: "pear config-help"
-* new command: "pear sign"
-* Windows support for "pear build" (requires
-  msdev)
-* new dependency type: "zend"
-* XML-RPC results may now be cached (see
-  cache_dir and cache_ttl config)
-* HTTP proxy authorization support
-* install/upgrade install-root support
-
-Bugfixes, Installer:
-* fix for XML-RPC bug that made some remote
-  commands fail
-* fix problems under Windows with
-  DIRECTORY_SEPARATOR
-* lots of other minor fixes
-* --force option did not work for "pear install
-  Package"
-* http downloader used "4.2.1" rather than
-  "PHP/4.2.1" as user agent
-* bending over a little more to figure out how
-  PHP is installed
-* "platform" file attribute was not included
-  during "pear package"
-
-New Features, PEAR Library:
-* added PEAR::loadExtension($ext)
-* added PEAR::delExpect()
-* System::mkTemp() now cleans up at shutdown
-* defined PEAR_ZE2 constant (boolean)
-* added PEAR::throwError() with a simpler API
-  than raiseError()
-
-Bugfixes, PEAR Library:
-* ZE2 compatibility fixes
-* use getenv() as fallback for $_ENV
-</notes>
-     <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="ge" version="0.4">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-     </deps>
-    </release>
-    <release>
-      <version>0.90</version>
-      <state>beta</state>
-      <date>2002-05-28</date>
-      <notes>
-* fix: "help" command was broken
-* new command: "info"
-* new command: "config-help"
-* un-indent multi-line data from xml description files
-* new command: "build"
-* fix: config-set did not work with "set" parameters
-* disable magic_quotes_runtime
-* "install" now builds and installs C extensions
-* added PEAR::delExpect()
-* System class no longer inherits PEAR
-* grouped PEAR_Config parameters
-* add --nobuild option to install/upgrade commands
-* new and more generic Frontend API
-</notes>
-      <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="has" version="0.4">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-      </deps>
-    </release>
-    <release>
-      <version>0.10</version>
-      <state>beta</state>
-      <date>2002-05-26</date>
-      <notes>
-Lots of stuff this time.  0.9 was not actually self-hosting, even
-though it claimed to be.  This version finally is self-hosting
-(really!), meaning you can upgrade the installer with the command
-"pear upgrade PEAR".
-
-* new config paramers: http_proxy and umask
-* HTTP proxy support when downloading packages
-* generalized command handling code
-* and fixed the bug that would not let commands have the
-  same options as "pear" itself
-* added long options to every command
-* added command shortcuts ("pear help shortcuts")
-* added stub for Gtk installer
-* some phpdoc fixes
-* added class dependency detector (using ext/tokenizer)
-* dependency handling fixes
-* added OS_Guess class for detecting OS
-* install files with the "platform" attribute set
-  only on matching operating systems
-* PEAR_Remote now falls back to the XML_RPC package
-  if xmlrpc-epi is not available
-* renamed command: package-list -> list
-* new command: package-dependencies
-* lots of minor fixes
-</notes>
-      <deps>
-       <dep type="php" rel="ge" version="4.1"/>
-       <dep type="pkg" rel="has" version="0.5">Archive_Tar</dep>
-       <dep type="pkg" rel="ge" version="0.11">Console_Getopt</dep>
-      </deps>
-    </release>
-    <release>
-      <version>0.9</version>
-      <state>beta</state>
-      <date>2002-04-07</date>
-      <notes>
-First package release.  Commands implemented:
-   remote-package-info
-   list-upgrades
-   list-remote-packages
-   download
-   config-show
-   config-get
-   config-set
-   list-installed
-   shell-test
-   install
-   uninstall
-   upgrade
-   package
-   package-list
-   package-info
-   login
-   logout
-</notes>
-    </release>
-  </changelog>
 </package>
index f67bdab606c55e6ceb0aff84ddad988544561d6e..05238b8fd26a3e4f3e59e29dc833bbd594cc411c 100644 (file)
@@ -1,5 +1,5 @@
 <!--
-     $Id: package.dtd,v 1.27.4.7 2003-07-08 20:39:45 pajoye Exp $
+     $Id: package.dtd,v 1.27.4.8 2003-09-29 14:06:38 pajoye Exp $
 
      This is the PEAR package description, version 1.0.
      It should be used with the informal public identifier:
index 0085b5338460d5810be416c578d49e6305020b93..d5b795c7aa456d9a5500e78530ac7cae4da63cbf 100755 (executable)
@@ -16,22 +16,22 @@ REM ----------------------------------------------------------------------
 REM  Authors:     Alexander Merz (alexmerz@php.net)\r
 REM ----------------------------------------------------------------------\r
 REM\r
-REM  $Id: pear.bat,v 1.15 2003/06/10 20:03:44 imajes Exp $\r
+REM  $Id: pear.bat,v 1.17 2003/08/29 21:21:27 cellog Exp $
 \r
 REM change this lines to match the paths of your system\r
 REM -------------------\r
 \r
 @ECHO OFF\r
 :: Check PEAR global ENV, set them if they do not exist\r
-IF "%PHP_PEAR_INSTALL_DIR%"=="" SET PHP_PEAR_INSTALL_DIR=@include_path@\r
-IF "%PHP_PEAR_BIN_DIR%"=="" SET PHP_PEAR_BIN_DIR=@bin_dir@\r
-IF "%PHP_PEAR_PHP_BIN%"=="" SET  PHP_PEAR_PHP_BIN=@php_bin@\r
+IF "%PHP_PEAR_INSTALL_DIR%"=="" SET "PHP_PEAR_INSTALL_DIR=@include_path@"
+IF "%PHP_PEAR_BIN_DIR%"=="" SET "PHP_PEAR_BIN_DIR=@bin_dir@"
+IF "%PHP_PEAR_PHP_BIN%"=="" SET "PHP_PEAR_PHP_BIN=@php_bin@"
  \r
 :: Check Folders and files\r
-IF NOT EXIST %PHP_PEAR_INSTALL_DIR% GOTO PEAR_INSTALL_ERROR\r
-IF NOT EXIST %PHP_PEAR_INSTALL_DIR%\pearcmd.php GOTO PEAR_INSTALL_ERROR2\r
-IF NOT EXIST %PHP_PEAR_BIN_DIR% GOTO PEAR_BIN_ERROR\r
-IF NOT EXIST %PHP_PEAR_PHP_BIN% GOTO PEAR_PHPBIN_ERROR\r
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%" GOTO PEAR_INSTALL_ERROR
+IF NOT EXIST "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" GOTO PEAR_INSTALL_ERROR2
+IF NOT EXIST "%PHP_PEAR_BIN_DIR%" GOTO PEAR_BIN_ERROR
+IF NOT EXIST "%PHP_PEAR_PHP_BIN%" GOTO PEAR_PHPBIN_ERROR
 :: launch pearcmd\r
 GOTO RUN\r
 :PEAR_INSTALL_ERROR\r
@@ -64,6 +64,6 @@ ECHO The current value is:
 ECHO %PHP_PEAR_PHP_BIN%\r
 GOTO END\r
 :RUN\r
-%PHP_PEAR_PHP_BIN% -C -d output_buffering=1 -d include_path=%PHP_PEAR_INSTALL_DIR% -f %PHP_PEAR_INSTALL_DIR%\pearcmd.php -- %1 %2 %3 %4 %5 %6 %7 %8 %9\r
+"%PHP_PEAR_PHP_BIN%" -C -d output_buffering=1 -d include_path="%PHP_PEAR_INSTALL_DIR%" -f "%PHP_PEAR_INSTALL_DIR%\pearcmd.php" -- %1 %2 %3 %4 %5 %6 %7 %8 %9
 :END\r
 @ECHO ON\r