]> wagnertech.de Git - timetracker.git/blobdiff - WEB-INF/lib/pear/PEAR/Installer.php
Initial repo created
[timetracker.git] / WEB-INF / lib / pear / PEAR / Installer.php
diff --git a/WEB-INF/lib/pear/PEAR/Installer.php b/WEB-INF/lib/pear/PEAR/Installer.php
new file mode 100644 (file)
index 0000000..eb17ca7
--- /dev/null
@@ -0,0 +1,1823 @@
+<?php
+/**
+ * PEAR_Installer
+ *
+ * PHP versions 4 and 5
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $
+ * @link       http://pear.php.net/package/PEAR
+ * @since      File available since Release 0.1
+ */
+
+/**
+ * Used for installation groups in package.xml 2.0 and platform exceptions
+ */
+require_once 'OS/Guess.php';
+require_once 'PEAR/Downloader.php';
+
+define('PEAR_INSTALLER_NOBINARY', -240);
+/**
+ * Administration class used to install PEAR packages and maintain the
+ * installed package database.
+ *
+ * @category   pear
+ * @package    PEAR
+ * @author     Stig Bakken <ssb@php.net>
+ * @author     Tomas V.V. Cox <cox@idecnet.com>
+ * @author     Martin Jansen <mj@php.net>
+ * @author     Greg Beaver <cellog@php.net>
+ * @copyright  1997-2009 The Authors
+ * @license    http://opensource.org/licenses/bsd-license.php New BSD License
+ * @version    Release: 1.9.4
+ * @link       http://pear.php.net/package/PEAR
+ * @since      Class available since Release 0.1
+ */
+class PEAR_Installer extends PEAR_Downloader
+{
+    // {{{ properties
+
+    /** name of the package directory, for example Foo-1.0
+     * @var string
+     */
+    var $pkgdir;
+
+    /** directory where PHP code files go
+     * @var string
+     */
+    var $phpdir;
+
+    /** directory where PHP extension files go
+     * @var string
+     */
+    var $extdir;
+
+    /** directory where documentation goes
+     * @var string
+     */
+    var $docdir;
+
+    /** installation root directory (ala PHP's INSTALL_ROOT or
+     * automake's DESTDIR
+     * @var string
+     */
+    var $installroot = '';
+
+    /** debug level
+     * @var int
+     */
+    var $debug = 1;
+
+    /** temporary directory
+     * @var string
+     */
+    var $tmpdir;
+
+    /**
+     * PEAR_Registry object used by the installer
+     * @var PEAR_Registry
+     */
+    var $registry;
+
+    /**
+     * array of PEAR_Downloader_Packages
+     * @var array
+     */
+    var $_downloadedPackages;
+
+    /** List of file transactions queued for an install/upgrade/uninstall.
+     *
+     *  Format:
+     *    array(
+     *      0 => array("rename => array("from-file", "to-file")),
+     *      1 => array("delete" => array("file-to-delete")),
+     *      ...
+     *    )
+     *
+     * @var array
+     */
+    var $file_operations = array();
+
+    // }}}
+
+    // {{{ constructor
+
+    /**
+     * PEAR_Installer constructor.
+     *
+     * @param object $ui user interface object (instance of PEAR_Frontend_*)
+     *
+     * @access public
+     */
+    function PEAR_Installer(&$ui)
+    {
+        parent::PEAR_Common();
+        $this->setFrontendObject($ui);
+        $this->debug = $this->config->get('verbose');
+    }
+
+    function setOptions($options)
+    {
+        $this->_options = $options;
+    }
+
+    function setConfig(&$config)
+    {
+        $this->config    = &$config;
+        $this->_registry = &$config->getRegistry();
+    }
+
+    // }}}
+
+    function _removeBackups($files)
+    {
+        foreach ($files as $path) {
+            $this->addFileOperation('removebackup', array($path));
+        }
+    }
+
+    // {{{ _deletePackageFiles()
+
+    /**
+     * Delete a package's installed files, does not remove empty directories.
+     *
+     * @param string package name
+     * @param string channel name
+     * @param bool if true, then files are backed up first
+     * @return bool TRUE on success, or a PEAR error on failure
+     * @access protected
+     */
+    function _deletePackageFiles($package, $channel = false, $backup = false)
+    {
+        if (!$channel) {
+            $channel = 'pear.php.net';
+        }
+
+        if (!strlen($package)) {
+            return $this->raiseError("No package to uninstall given");
+        }
+
+        if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
+            // to avoid race conditions, include all possible needed files
+            require_once 'PEAR/Task/Common.php';
+            require_once 'PEAR/Task/Replace.php';
+            require_once 'PEAR/Task/Unixeol.php';
+            require_once 'PEAR/Task/Windowseol.php';
+            require_once 'PEAR/PackageFile/v1.php';
+            require_once 'PEAR/PackageFile/v2.php';
+            require_once 'PEAR/PackageFile/Generator/v1.php';
+            require_once 'PEAR/PackageFile/Generator/v2.php';
+        }
+
+        $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
+        if ($filelist == null) {
+            return $this->raiseError("$channel/$package not installed");
+        }
+
+        $ret = array();
+        foreach ($filelist as $file => $props) {
+            if (empty($props['installed_as'])) {
+                continue;
+            }
+
+            $path = $props['installed_as'];
+            if ($backup) {
+                $this->addFileOperation('backup', array($path));
+                $ret[] = $path;
+            }
+
+            $this->addFileOperation('delete', array($path));
+        }
+
+        if ($backup) {
+            return $ret;
+        }
+
+        return true;
+    }
+
+    // }}}
+    // {{{ _installFile()
+
+    /**
+     * @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)
+    {
+        // {{{ return if this file is meant for another platform
+        static $os;
+        if (!isset($this->_registry)) {
+            $this->_registry = &$this->config->getRegistry();
+        }
+
+        if (isset($atts['platform'])) {
+            if (empty($os)) {
+                $os = new OS_Guess();
+            }
+
+            if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
+                $negate   = true;
+                $platform = substr($atts['platform'], 1);
+            } else {
+                $negate    = false;
+                $platform = $atts['platform'];
+            }
+
+            if ((bool) $os->matchSignature($platform) === $negate) {
+                $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
+                return PEAR_INSTALLER_SKIPPED;
+            }
+        }
+        // }}}
+
+        $channel = $this->pkginfo->getChannel();
+        // {{{ assemble the destination paths
+        switch ($atts['role']) {
+            case 'src':
+            case 'extsrc':
+                $this->source_files++;
+                return;
+            case 'doc':
+            case 'data':
+            case 'test':
+                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
+                            DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
+                unset($atts['baseinstalldir']);
+                break;
+            case 'ext':
+            case 'php':
+                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
+                break;
+            case 'script':
+                $dest_dir = $this->config->get('bin_dir', null, $channel);
+                break;
+            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'];
+        }
+
+        if (dirname($file) != '.' && empty($atts['install-as'])) {
+            $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
+        }
+
+        if (empty($atts['install-as'])) {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
+        } else {
+            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
+        }
+        $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
+
+        // Clean up the DIRECTORY_SEPARATOR mess
+        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
+        list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
+                                                    array(DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR,
+                                                          DIRECTORY_SEPARATOR),
+                                                    array($dest_file, $orig_file));
+        $final_dest_file = $installed_as = $dest_file;
+        if (isset($this->_options['packagingroot'])) {
+            $installedas_dest_dir  = dirname($final_dest_file);
+            $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+            $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
+        } else {
+            $installedas_dest_dir  = dirname($final_dest_file);
+            $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        }
+
+        $dest_dir  = dirname($final_dest_file);
+        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+        }
+        // }}}
+
+        if (empty($this->_options['register-only']) &&
+              (!file_exists($dest_dir) || !is_dir($dest_dir))) {
+            if (!$this->mkDirHier($dest_dir)) {
+                return $this->raiseError("failed to mkdir $dest_dir",
+                                         PEAR_INSTALLER_FAILED);
+            }
+            $this->log(3, "+ mkdir $dest_dir");
+        }
+
+        // pretty much nothing happens if we are only registering the install
+        if (empty($this->_options['register-only'])) {
+            if (empty($atts['replacements'])) {
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (!@copy($orig_file, $dest_file)) {
+                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $orig_file $dest_file");
+                if (isset($atts['md5sum'])) {
+                    $md5sum = md5_file($dest_file);
+                }
+            } else {
+                // {{{ file with replacements
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $contents = file_get_contents($orig_file);
+                if ($contents === false) {
+                    $contents = '';
+                }
+
+                if (isset($atts['md5sum'])) {
+                    $md5sum = md5($contents);
+                }
+
+                $subst_from = $subst_to = array();
+                foreach ($atts['replacements'] as $a) {
+                    $to = '';
+                    if ($a['type'] == 'php-const') {
+                        if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
+                            eval("\$to = $a[to];");
+                        } else {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid php-const replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    } elseif ($a['type'] == 'pear-config') {
+                        if ($a['to'] == 'master_server') {
+                            $chan = $this->_registry->getChannel($channel);
+                            if (!PEAR::isError($chan)) {
+                                $to = $chan->getServer();
+                            } else {
+                                $to = $this->config->get($a['to'], null, $channel);
+                            }
+                        } else {
+                            $to = $this->config->get($a['to'], null, $channel);
+                        }
+                        if (is_null($to)) {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid pear-config replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    } elseif ($a['type'] == 'package-info') {
+                        if ($t = $this->pkginfo->packageInfo($a['to'])) {
+                            $to = $t;
+                        } else {
+                            if (!isset($options['soft'])) {
+                                $this->log(0, "invalid package-info replacement: $a[to]");
+                            }
+                            continue;
+                        }
+                    }
+                    if (!is_null($to)) {
+                        $subst_from[] = $a['from'];
+                        $subst_to[] = $to;
+                    }
+                }
+
+                $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
+                if (sizeof($subst_from)) {
+                    $contents = str_replace($subst_from, $subst_to, $contents);
+                }
+
+                $wp = @fopen($dest_file, "wb");
+                if (!is_resource($wp)) {
+                    return $this->raiseError("failed to create $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (@fwrite($wp, $contents) === false) {
+                    return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                fclose($wp);
+                // }}}
+            }
+
+            // {{{ check the md5
+            if (isset($md5sum)) {
+                if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
+                    $this->log(2, "md5sum ok: $final_dest_file");
+                } else {
+                    if (empty($options['force'])) {
+                        // delete the file
+                        if (file_exists($dest_file)) {
+                            unlink($dest_file);
+                        }
+
+                        if (!isset($options['ignore-errors'])) {
+                            return $this->raiseError("bad md5sum for file $final_dest_file",
+                                                 PEAR_INSTALLER_FAILED);
+                        }
+
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    } else {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    }
+                }
+            }
+            // }}}
+            // {{{ set file permissions
+            if (!OS_WINDOWS) {
+                if ($atts['role'] == 'script') {
+                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
+                    $this->log(3, "+ chmod +x $dest_file");
+                } else {
+                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+                }
+
+                if ($atts['role'] != 'src') {
+                    $this->addFileOperation("chmod", array($mode, $dest_file));
+                    if (!@chmod($dest_file, $mode)) {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+                        }
+                    }
+                }
+            }
+            // }}}
+
+            if ($atts['role'] == 'src') {
+                rename($dest_file, $final_dest_file);
+                $this->log(2, "renamed source file $dest_file to $final_dest_file");
+            } else {
+                $this->addFileOperation("rename", array($dest_file, $final_dest_file,
+                    $atts['role'] == 'ext'));
+            }
+        }
+
+        // Store the full path where the file was installed for easy unistall
+        if ($atts['role'] != 'script') {
+            $loc = $this->config->get($atts['role'] . '_dir');
+        } else {
+            $loc = $this->config->get('bin_dir');
+        }
+
+        if ($atts['role'] != 'src') {
+            $this->addFileOperation("installed_as", array($file, $installed_as,
+                                    $loc,
+                                    dirname(substr($installedas_dest_file, strlen($loc)))));
+        }
+
+        //$this->log(2, "installed: $dest_file");
+        return PEAR_INSTALLER_OK;
+    }
+
+    // }}}
+    // {{{ _installFile2()
+
+    /**
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     * @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 _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
+    {
+        $atts = $real_atts;
+        if (!isset($this->_registry)) {
+            $this->_registry = &$this->config->getRegistry();
+        }
+
+        $channel = $pkg->getChannel();
+        // {{{ assemble the destination paths
+        if (!in_array($atts['attribs']['role'],
+              PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
+            return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
+                    "' for file $file");
+        }
+
+        $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
+        $err  = $role->setup($this, $pkg, $atts['attribs'], $file);
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+
+        if (!$role->isInstallable()) {
+            return;
+        }
+
+        $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
+        if (PEAR::isError($info)) {
+            return $info;
+        }
+
+        list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
+        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
+            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
+        }
+
+        $final_dest_file = $installed_as = $dest_file;
+        if (isset($this->_options['packagingroot'])) {
+            $final_dest_file = $this->_prependPath($final_dest_file,
+                $this->_options['packagingroot']);
+        }
+
+        $dest_dir  = dirname($final_dest_file);
+        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
+        // }}}
+
+        if (empty($this->_options['register-only'])) {
+            if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
+                if (!$this->mkDirHier($dest_dir)) {
+                    return $this->raiseError("failed to mkdir $dest_dir",
+                                             PEAR_INSTALLER_FAILED);
+                }
+                $this->log(3, "+ mkdir $dest_dir");
+            }
+        }
+
+        $attribs = $atts['attribs'];
+        unset($atts['attribs']);
+        // pretty much nothing happens if we are only registering the install
+        if (empty($this->_options['register-only'])) {
+            if (!count($atts)) { // no tasks
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                if (!@copy($orig_file, $dest_file)) {
+                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $orig_file $dest_file");
+                if (isset($attribs['md5sum'])) {
+                    $md5sum = md5_file($dest_file);
+                }
+            } else { // file with tasks
+                if (!file_exists($orig_file)) {
+                    return $this->raiseError("file $orig_file does not exist",
+                                             PEAR_INSTALLER_FAILED);
+                }
+
+                $contents = file_get_contents($orig_file);
+                if ($contents === false) {
+                    $contents = '';
+                }
+
+                if (isset($attribs['md5sum'])) {
+                    $md5sum = md5($contents);
+                }
+
+                foreach ($atts as $tag => $raw) {
+                    $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
+                    $task = "PEAR_Task_$tag";
+                    $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
+                    if (!$task->isScript()) { // scripts are only handled after installation
+                        $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
+                        $res = $task->startSession($pkg, $contents, $final_dest_file);
+                        if ($res === false) {
+                            continue; // skip this file
+                        }
+
+                        if (PEAR::isError($res)) {
+                            return $res;
+                        }
+
+                        $contents = $res; // save changes
+                    }
+
+                    $wp = @fopen($dest_file, "wb");
+                    if (!is_resource($wp)) {
+                        return $this->raiseError("failed to create $dest_file: $php_errormsg",
+                                                 PEAR_INSTALLER_FAILED);
+                    }
+
+                    if (fwrite($wp, $contents) === false) {
+                        return $this->raiseError("failed writing to $dest_file: $php_errormsg",
+                                                 PEAR_INSTALLER_FAILED);
+                    }
+
+                    fclose($wp);
+                }
+            }
+
+            // {{{ check the md5
+            if (isset($md5sum)) {
+                // Make sure the original md5 sum matches with expected
+                if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
+                    $this->log(2, "md5sum ok: $final_dest_file");
+
+                    if (isset($contents)) {
+                        // set md5 sum based on $content in case any tasks were run.
+                        $real_atts['attribs']['md5sum'] = md5($contents);
+                    }
+                } else {
+                    if (empty($options['force'])) {
+                        // delete the file
+                        if (file_exists($dest_file)) {
+                            unlink($dest_file);
+                        }
+
+                        if (!isset($options['ignore-errors'])) {
+                            return $this->raiseError("bad md5sum for file $final_dest_file",
+                                                     PEAR_INSTALLER_FAILED);
+                        }
+
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    } else {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
+                        }
+                    }
+                }
+            } else {
+                $real_atts['attribs']['md5sum'] = md5_file($dest_file);
+            }
+
+            // }}}
+            // {{{ set file permissions
+            if (!OS_WINDOWS) {
+                if ($role->isExecutable()) {
+                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
+                    $this->log(3, "+ chmod +x $dest_file");
+                } else {
+                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+                }
+
+                if ($attribs['role'] != 'src') {
+                    $this->addFileOperation("chmod", array($mode, $dest_file));
+                    if (!@chmod($dest_file, $mode)) {
+                        if (!isset($options['soft'])) {
+                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
+                        }
+                    }
+                }
+            }
+            // }}}
+
+            if ($attribs['role'] == 'src') {
+                rename($dest_file, $final_dest_file);
+                $this->log(2, "renamed source file $dest_file to $final_dest_file");
+            } else {
+                $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
+            }
+        }
+
+        // Store the full path where the file was installed for easy uninstall
+        if ($attribs['role'] != 'src') {
+            $loc = $this->config->get($role->getLocationConfig(), null, $channel);
+            $this->addFileOperation('installed_as', array($file, $installed_as,
+                                $loc,
+                                dirname(substr($installed_as, strlen($loc)))));
+        }
+
+        //$this->log(2, "installed: $dest_file");
+        return PEAR_INSTALLER_OK;
+    }
+
+    // }}}
+    // {{{ addFileOperation()
+
+    /**
+     * Add a file operation to the current file transaction.
+     *
+     * @see startFileTransaction()
+     * @param string $type This can be one of:
+     *    - rename:  rename a file ($data has 3 values)
+     *    - backup:  backup an existing file ($data has 1 value)
+     *    - removebackup:  clean up backups created during install ($data has 1 value)
+     *    - 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).
+     * @param 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 third whether this is a PHP extension.
+     *
+     *    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]");
+        } else {
+            $this->log(3, "adding to transaction: $type " . implode(" ", $data));
+        }
+        $this->file_operations[] = array($type, $data);
+    }
+
+    // }}}
+    // {{{ startFileTransaction()
+
+    function startFileTransaction($rollback_in_case = false)
+    {
+        if (count($this->file_operations) && $rollback_in_case) {
+            $this->rollbackFileTransaction();
+        }
+        $this->file_operations = array();
+    }
+
+    // }}}
+    // {{{ commitFileTransaction()
+
+    function commitFileTransaction()
+    {
+        // {{{ first, check permissions and such manually
+        $errors = array();
+        foreach ($this->file_operations as $key => $tr) {
+            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]";
+                    }
+                    break;
+                case 'chmod':
+                    // check that file is writable
+                    if (!is_writable($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])) {
+                        if (!is_writable(dirname($data[0]))) {
+                            $errors[] = "permission denied ($type): $data[0]";
+                        } else {
+                            // make sure the file to be deleted can be opened for writing
+                            $fp = false;
+                            if (!is_dir($data[0]) &&
+                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
+                                $errors[] = "permission denied ($type): $data[0]";
+                            } elseif ($fp) {
+                                fclose($fp);
+                            }
+                        }
+
+                        /* Verify we are not deleting a file owned by another package
+                         * This can happen when a file moves from package A to B in
+                         * an upgrade ala http://pear.php.net/17986
+                         */
+                        $info = array(
+                            'package' => strtolower($this->pkginfo->getName()),
+                            'channel' => strtolower($this->pkginfo->getChannel()),
+                        );
+                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
+                        if (is_array($result)) {
+                            $res = array_diff($result, $info);
+                            if (!empty($res)) {
+                                $new = $this->_registry->getPackage($result[1], $result[0]);
+                                $this->file_operations[$key] = false;
+                                $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled.");
+                            }
+                        }
+                    }
+                    break;
+            }
+
+        }
+        // }}}
+
+        $n = count($this->file_operations);
+        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
+
+        $m = count($errors);
+        if ($m > 0) {
+            foreach ($errors as $error) {
+                if (!isset($this->_options['soft'])) {
+                    $this->log(1, $error);
+                }
+            }
+
+            if (!isset($this->_options['ignore-errors'])) {
+                return false;
+            }
+        }
+
+        $this->_dirtree = array();
+        // {{{ really commit the transaction
+        foreach ($this->file_operations as $i => $tr) {
+            if (!$tr) {
+                // support removal of non-existing backups
+                continue;
+            }
+
+            list($type, $data) = $tr;
+            switch ($type) {
+                case 'backup':
+                    if (!file_exists($data[0])) {
+                        $this->file_operations[$i] = false;
+                        break;
+                    }
+
+                    if (!@copy($data[0], $data[0] . '.bak')) {
+                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
+                            '.bak ' . $php_errormsg);
+                        return false;
+                    }
+                    $this->log(3, "+ backup $data[0] to $data[0].bak");
+                    break;
+                case 'removebackup':
+                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+                        unlink($data[0] . '.bak');
+                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+                    }
+                    break;
+                case 'rename':
+                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
+                    if (!$test && file_exists($data[1])) {
+                        if ($data[2]) {
+                            $extra = ', this extension must be installed manually.  Rename to "' .
+                                basename($data[1]) . '"';
+                        } else {
+                            $extra = '';
+                        }
+
+                        if (!isset($this->_options['soft'])) {
+                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
+                                $data[0] . $extra);
+                        }
+
+                        if (!isset($this->_options['ignore-errors'])) {
+                            return false;
+                        }
+                    }
+
+                    // permissions issues with rename - copy() is far superior
+                    $perms = @fileperms($data[0]);
+                    if (!@copy($data[0], $data[1])) {
+                        $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
+                            ' ' . $php_errormsg);
+                        return false;
+                    }
+
+                    // copy over permissions, otherwise they are lost
+                    @chmod($data[1], $perms);
+                    @unlink($data[0]);
+                    $this->log(3, "+ mv $data[0] $data[1]");
+                    break;
+                case 'chmod':
+                    if (!@chmod($data[1], $data[0])) {
+                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
+                            decoct($data[0]) . ' ' . $php_errormsg);
+                        return false;
+                    }
+
+                    $octmode = decoct($data[0]);
+                    $this->log(3, "+ chmod $octmode $data[1]");
+                    break;
+                case 'delete':
+                    if (file_exists($data[0])) {
+                        if (!@unlink($data[0])) {
+                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
+                                $php_errormsg);
+                            return false;
+                        }
+                        $this->log(3, "+ rm $data[0]");
+                    }
+                    break;
+                case 'rmdir':
+                    if (file_exists($data[0])) {
+                        do {
+                            $testme = opendir($data[0]);
+                            while (false !== ($entry = readdir($testme))) {
+                                if ($entry == '.' || $entry == '..') {
+                                    continue;
+                                }
+                                closedir($testme);
+                                break 2; // this directory is not empty and can't be
+                                         // deleted
+                            }
+
+                            closedir($testme);
+                            if (!@rmdir($data[0])) {
+                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
+                                    $php_errormsg);
+                                return false;
+                            }
+                            $this->log(3, "+ rmdir $data[0]");
+                        } while (false);
+                    }
+                    break;
+                case 'installed_as':
+                    $this->pkginfo->setInstalledAs($data[0], $data[1]);
+                    if (!isset($this->_dirtree[dirname($data[1])])) {
+                        $this->_dirtree[dirname($data[1])] = true;
+                        $this->pkginfo->setDirtree(dirname($data[1]));
+
+                        while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
+                                $data[3] != '/' && $data[3] != '\\') {
+                            $this->pkginfo->setDirtree($pp =
+                                $this->_prependPath($data[3], $data[2]));
+                            $this->_dirtree[$pp] = true;
+                            $data[3] = dirname($data[3]);
+                        }
+                    }
+                    break;
+            }
+        }
+        // }}}
+        $this->log(2, "successfully committed $n file operations");
+        $this->file_operations = array();
+        return true;
+    }
+
+    // }}}
+    // {{{ rollbackFileTransaction()
+
+    function rollbackFileTransaction()
+    {
+        $n = count($this->file_operations);
+        $this->log(2, "rolling back $n file operations");
+        foreach ($this->file_operations as $tr) {
+            list($type, $data) = $tr;
+            switch ($type) {
+                case 'backup':
+                    if (file_exists($data[0] . '.bak')) {
+                        if (file_exists($data[0] && is_writable($data[0]))) {
+                            unlink($data[0]);
+                        }
+                        @copy($data[0] . '.bak', $data[0]);
+                        $this->log(3, "+ restore $data[0] from $data[0].bak");
+                    }
+                    break;
+                case 'removebackup':
+                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
+                        unlink($data[0] . '.bak');
+                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
+                    }
+                    break;
+                case 'rename':
+                    @unlink($data[0]);
+                    $this->log(3, "+ rm $data[0]");
+                    break;
+                case 'mkdir':
+                    @rmdir($data[0]);
+                    $this->log(3, "+ rmdir $data[0]");
+                    break;
+                case 'chmod':
+                    break;
+                case 'delete':
+                    break;
+                case 'installed_as':
+                    $this->pkginfo->setInstalledAs($data[0], false);
+                    break;
+            }
+        }
+        $this->pkginfo->resetDirtree();
+        $this->file_operations = array();
+    }
+
+    // }}}
+    // {{{ mkDirHier($dir)
+
+    function mkDirHier($dir)
+    {
+        $this->addFileOperation('mkdir', array($dir));
+        return parent::mkDirHier($dir);
+    }
+
+    // }}}
+    // {{{ 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
+     * @deprecated in favor of PEAR_Downloader
+     */
+    function download($packages, $options, &$config, &$installpackages,
+                      &$errors, $installed = false, $willinstall = false, $state = false)
+    {
+        // trickiness: initialize here
+        parent::PEAR_Downloader($this->ui, $options, $config);
+        $ret             = parent::download($packages);
+        $errors          = $this->getErrorMsgs();
+        $installpackages = $this->getDownloadedPackages();
+        trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
+                      "in favor of PEAR_Downloader class", E_USER_WARNING);
+        return $ret;
+    }
+
+    // }}}
+    // {{{ _parsePackageXml()
+
+    function _parsePackageXml(&$descfile)
+    {
+        // Parse xml file -----------------------------------------------
+        $pkg = new PEAR_PackageFile($this->config, $this->debug);
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($p)) {
+            if (is_array($p->getUserInfo())) {
+                foreach ($p->getUserInfo() as $err) {
+                    $loglevel = $err['level'] == 'error' ? 0 : 1;
+                    if (!isset($this->_options['soft'])) {
+                        $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
+                    }
+                }
+            }
+            return $this->raiseError('Installation failed: invalid package file');
+        }
+
+        $descfile = $p->getPackageFile();
+        return $p;
+    }
+
+    // }}}
+    /**
+     * Set the list of PEAR_Downloader_Package objects to allow more sane
+     * dependency validation
+     * @param array
+     */
+    function setDownloadedPackages(&$pkgs)
+    {
+        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+        $err = $this->analyzeDependencies($pkgs);
+        PEAR::popErrorHandling();
+        if (PEAR::isError($err)) {
+            return $err;
+        }
+        $this->_downloadedPackages = &$pkgs;
+    }
+
+    /**
+     * Set the list of PEAR_Downloader_Package objects to allow more sane
+     * dependency validation
+     * @param array
+     */
+    function setUninstallPackages(&$pkgs)
+    {
+        $this->_downloadedPackages = &$pkgs;
+    }
+
+    function getInstallPackages()
+    {
+        return $this->_downloadedPackages;
+    }
+
+    // {{{ install()
+
+    /**
+     * Installs the files within the package file specified.
+     *
+     * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
+     *        or a pre-initialized packagefile object
+     * @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|PEAR_Error package info if successful
+     */
+    function install($pkgfile, $options = array())
+    {
+        $this->_options = $options;
+        $this->_registry = &$this->config->getRegistry();
+        if (is_object($pkgfile)) {
+            $dlpkg    = &$pkgfile;
+            $pkg      = $pkgfile->getPackageFile();
+            $pkgfile  = $pkg->getArchiveFile();
+            $descfile = $pkg->getPackageFile();
+        } else {
+            $descfile = $pkgfile;
+            $pkg      = $this->_parsePackageXml($descfile);
+            if (PEAR::isError($pkg)) {
+                return $pkg;
+            }
+        }
+
+        $tmpdir = dirname($descfile);
+        if (realpath($descfile) != realpath($pkgfile)) {
+            // Use the temp_dir since $descfile can contain the download dir path
+            $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
+            $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
+
+            $tar = new Archive_Tar($pkgfile);
+            if (!$tar->extract($tmpdir)) {
+                return $this->raiseError("unable to unpack $pkgfile");
+            }
+        }
+
+        $pkgname = $pkg->getName();
+        $channel = $pkg->getChannel();
+        if (isset($this->_options['packagingroot'])) {
+            $regdir = $this->_prependPath(
+                $this->config->get('php_dir', null, 'pear.php.net'),
+                $this->_options['packagingroot']);
+
+            $packrootphp_dir = $this->_prependPath(
+                $this->config->get('php_dir', null, $channel),
+                $this->_options['packagingroot']);
+        }
+
+        if (isset($options['installroot'])) {
+            $this->config->setInstallRoot($options['installroot']);
+            $this->_registry = &$this->config->getRegistry();
+            $installregistry = &$this->_registry;
+            $this->installroot = ''; // all done automagically now
+            $php_dir = $this->config->get('php_dir', null, $channel);
+        } else {
+            $this->config->setInstallRoot(false);
+            $this->_registry = &$this->config->getRegistry();
+            if (isset($this->_options['packagingroot'])) {
+                $installregistry = &new PEAR_Registry($regdir);
+                if (!$installregistry->channelExists($channel, true)) {
+                    // we need to fake a channel-discover of this channel
+                    $chanobj = $this->_registry->getChannel($channel, true);
+                    $installregistry->addChannel($chanobj);
+                }
+                $php_dir = $packrootphp_dir;
+            } else {
+                $installregistry = &$this->_registry;
+                $php_dir = $this->config->get('php_dir', null, $channel);
+            }
+            $this->installroot = '';
+        }
+
+        // {{{ checks to do when not in "force" mode
+        if (empty($options['force']) &&
+              (file_exists($this->config->get('php_dir')) &&
+               is_dir($this->config->get('php_dir')))) {
+            $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
+            $instfilelist = $pkg->getInstallationFileList(true);
+            if (PEAR::isError($instfilelist)) {
+                return $instfilelist;
+            }
+
+            // ensure we have the most accurate registry
+            $installregistry->flushFileMap();
+            $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
+            if (PEAR::isError($test)) {
+                return $test;
+            }
+
+            if (sizeof($test)) {
+                $pkgs = $this->getInstallPackages();
+                $found = false;
+                foreach ($pkgs as $param) {
+                    if ($pkg->isSubpackageOf($param)) {
+                        $found = true;
+                        break;
+                    }
+                }
+
+                if ($found) {
+                    // subpackages can conflict with earlier versions of parent packages
+                    $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
+                    $tmp = $test;
+                    foreach ($tmp as $file => $info) {
+                        if (is_array($info)) {
+                            if (strtolower($info[1]) == strtolower($param->getPackage()) &&
+                                  strtolower($info[0]) == strtolower($param->getChannel())
+                            ) {
+                                if (isset($parentreg['filelist'][$file])) {
+                                    unset($parentreg['filelist'][$file]);
+                                } else{
+                                    $pos     = strpos($file, '/');
+                                    $basedir = substr($file, 0, $pos);
+                                    $file2   = substr($file, $pos + 1);
+                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+                                    ) {
+                                        unset($parentreg['filelist'][$file2]);
+                                    }
+                                }
+
+                                unset($test[$file]);
+                            }
+                        } else {
+                            if (strtolower($param->getChannel()) != 'pear.php.net') {
+                                continue;
+                            }
+
+                            if (strtolower($info) == strtolower($param->getPackage())) {
+                                if (isset($parentreg['filelist'][$file])) {
+                                    unset($parentreg['filelist'][$file]);
+                                } else{
+                                    $pos     = strpos($file, '/');
+                                    $basedir = substr($file, 0, $pos);
+                                    $file2   = substr($file, $pos + 1);
+                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
+                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
+                                    ) {
+                                        unset($parentreg['filelist'][$file2]);
+                                    }
+                                }
+
+                                unset($test[$file]);
+                            }
+                        }
+                    }
+
+                    $pfk = &new PEAR_PackageFile($this->config);
+                    $parentpkg = &$pfk->fromArray($parentreg);
+                    $installregistry->updatePackage2($parentpkg);
+                }
+
+                if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
+                    $tmp = $test;
+                    foreach ($tmp as $file => $info) {
+                        if (is_string($info)) {
+                            // pear.php.net packages are always stored as strings
+                            if (strtolower($info) == strtolower($param->getPackage())) {
+                                // upgrading existing package
+                                unset($test[$file]);
+                            }
+                        }
+                    }
+                }
+
+                if (count($test)) {
+                    $msg = "$channel/$pkgname: conflicting files found:\n";
+                    $longest = max(array_map("strlen", array_keys($test)));
+                    $fmt = "%${longest}s (%s)\n";
+                    foreach ($test as $file => $info) {
+                        if (!is_array($info)) {
+                            $info = array('pear.php.net', $info);
+                        }
+                        $info = $info[0] . '/' . $info[1];
+                        $msg .= sprintf($fmt, $file, $info);
+                    }
+
+                    if (!isset($options['ignore-errors'])) {
+                        return $this->raiseError($msg);
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->log(0, "WARNING: $msg");
+                    }
+                }
+            }
+        }
+        // }}}
+
+        $this->startFileTransaction();
+
+        $usechannel = $channel;
+        if ($channel == 'pecl.php.net') {
+            $test = $installregistry->packageExists($pkgname, $channel);
+            if (!$test) {
+                $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+                $usechannel = 'pear.php.net';
+            }
+        } else {
+            $test = $installregistry->packageExists($pkgname, $channel);
+        }
+
+        if (empty($options['upgrade']) && empty($options['soft'])) {
+            // checks to do only when installing new packages
+            if (empty($options['force']) && $test) {
+                return $this->raiseError("$channel/$pkgname is already installed");
+            }
+        } else {
+            // Upgrade
+            if ($test) {
+                $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
+                $v2 = $pkg->getVersion();
+                $cmp = version_compare("$v1", "$v2", 'gt');
+                if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
+                    return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
+                }
+            }
+        }
+
+        // Do cleanups for upgrade and install, remove old release's files first
+        if ($test && empty($options['register-only'])) {
+            // when upgrading, remove old release's files first:
+            if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
+                  true))) {
+                if (!isset($options['ignore-errors'])) {
+                    return $this->raiseError($err);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: ' . $err->getMessage());
+                }
+            } else {
+                $backedup = $err;
+            }
+        }
+
+        // {{{ Copy files to dest dir ---------------------------------------
+
+        // info from the package it self we want to access from _installFile
+        $this->pkginfo = &$pkg;
+        // used to determine whether we should build any C code
+        $this->source_files = 0;
+
+        $savechannel = $this->config->get('default_channel');
+        if (empty($options['register-only']) && !is_dir($php_dir)) {
+            if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
+                return $this->raiseError("no installation destination directory '$php_dir'\n");
+            }
+        }
+
+        if (substr($pkgfile, -4) != '.xml') {
+            $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
+        }
+
+        $this->configSet('default_channel', $channel);
+        // {{{ install files
+
+        $ver = $pkg->getPackagexmlVersion();
+        if (version_compare($ver, '2.0', '>=')) {
+            $filelist = $pkg->getInstallationFilelist();
+        } else {
+            $filelist = $pkg->getFileList();
+        }
+
+        if (PEAR::isError($filelist)) {
+            return $filelist;
+        }
+
+        $p = &$installregistry->getPackage($pkgname, $channel);
+        $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
+
+        $pkg->resetFilelist();
+        $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
+            'version', $pkg->getChannel()));
+        foreach ($filelist as $file => $atts) {
+            $this->expectError(PEAR_INSTALLER_FAILED);
+            if ($pkg->getPackagexmlVersion() == '1.0') {
+                $res = $this->_installFile($file, $atts, $tmpdir, $options);
+            } else {
+                $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $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);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, "Warning: " . $res->getMessage());
+                }
+            }
+
+            $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
+            if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
+                // Register files that were installed
+                $pkg->installedFile($file, $atts);
+            }
+        }
+        // }}}
+
+        // {{{ compile and install source files
+        if ($this->source_files > 0 && empty($options['nobuild'])) {
+            if (PEAR::isError($err =
+                  $this->_compileSourceFiles($savechannel, $pkg))) {
+                return $err;
+            }
+        }
+        // }}}
+
+        if (isset($backedup)) {
+            $this->_removeBackups($backedup);
+        }
+
+        if (!$this->commitFileTransaction()) {
+            $this->rollbackFileTransaction();
+            $this->configSet('default_channel', $savechannel);
+            return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
+        }
+        // }}}
+
+        $ret          = false;
+        $installphase = 'install';
+        $oldversion   = false;
+        // {{{ Register that the package is installed -----------------------
+        if (empty($options['upgrade'])) {
+            // if 'force' is used, replace the info in registry
+            $usechannel = $channel;
+            if ($channel == 'pecl.php.net') {
+                $test = $installregistry->packageExists($pkgname, $channel);
+                if (!$test) {
+                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+                    $usechannel = 'pear.php.net';
+                }
+            } else {
+                $test = $installregistry->packageExists($pkgname, $channel);
+            }
+
+            if (!empty($options['force']) && $test) {
+                $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
+                $installregistry->deletePackage($pkgname, $usechannel);
+            }
+            $ret = $installregistry->addPackage2($pkg);
+        } else {
+            if ($dirtree) {
+                $this->startFileTransaction();
+                // attempt to delete empty directories
+                uksort($dirtree, array($this, '_sortDirs'));
+                foreach($dirtree as $dir => $notused) {
+                    $this->addFileOperation('rmdir', array($dir));
+                }
+                $this->commitFileTransaction();
+            }
+
+            $usechannel = $channel;
+            if ($channel == 'pecl.php.net') {
+                $test = $installregistry->packageExists($pkgname, $channel);
+                if (!$test) {
+                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
+                    $usechannel = 'pear.php.net';
+                }
+            } else {
+                $test = $installregistry->packageExists($pkgname, $channel);
+            }
+
+            // new: upgrade installs a package if it isn't installed
+            if (!$test) {
+                $ret = $installregistry->addPackage2($pkg);
+            } else {
+                if ($usechannel != $channel) {
+                    $installregistry->deletePackage($pkgname, $usechannel);
+                    $ret = $installregistry->addPackage2($pkg);
+                } else {
+                    $ret = $installregistry->updatePackage2($pkg);
+                }
+                $installphase = 'upgrade';
+            }
+        }
+
+        if (!$ret) {
+            $this->configSet('default_channel', $savechannel);
+            return $this->raiseError("Adding package $channel/$pkgname to registry failed");
+        }
+        // }}}
+
+        $this->configSet('default_channel', $savechannel);
+        if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
+            if (PEAR_Task_Common::hasPostinstallTasks()) {
+                PEAR_Task_Common::runPostinstallTasks($installphase);
+            }
+        }
+
+        return $pkg->toArray(true);
+    }
+
+    // }}}
+
+    // {{{ _compileSourceFiles()
+    /**
+     * @param string
+     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
+     */
+    function _compileSourceFiles($savechannel, &$filelist)
+    {
+        require_once 'PEAR/Builder.php';
+        $this->log(1, "$this->source_files source files, building");
+        $bob = &new PEAR_Builder($this->ui);
+        $bob->debug = $this->debug;
+        $built = $bob->build($filelist, array(&$this, '_buildCallback'));
+        if (PEAR::isError($built)) {
+            $this->rollbackFileTransaction();
+            $this->configSet('default_channel', $savechannel);
+            return $built;
+        }
+
+        $this->log(1, "\nBuild process completed successfully");
+        foreach ($built as $ext) {
+            $bn = basename($ext['file']);
+            list($_ext_name, $_ext_suff) = explode('.', $bn);
+            if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
+                if (extension_loaded($_ext_name)) {
+                    $this->raiseError("Extension '$_ext_name' already loaded. " .
+                                      'Please unload it in your php.ini file ' .
+                                      'prior to install or upgrade');
+                }
+                $role = 'ext';
+            } else {
+                $role = 'src';
+            }
+
+            $dest = $ext['dest'];
+            $packagingroot = '';
+            if (isset($this->_options['packagingroot'])) {
+                $packagingroot = $this->_options['packagingroot'];
+            }
+
+            $copyto = $this->_prependPath($dest, $packagingroot);
+            $extra  = $copyto != $dest ? " as '$copyto'" : '';
+            $this->log(1, "Installing '$dest'$extra");
+
+            $copydir = dirname($copyto);
+            // pretty much nothing happens if we are only registering the install
+            if (empty($this->_options['register-only'])) {
+                if (!file_exists($copydir) || !is_dir($copydir)) {
+                    if (!$this->mkDirHier($copydir)) {
+                        return $this->raiseError("failed to mkdir $copydir",
+                            PEAR_INSTALLER_FAILED);
+                    }
+
+                    $this->log(3, "+ mkdir $copydir");
+                }
+
+                if (!@copy($ext['file'], $copyto)) {
+                    return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
+                }
+
+                $this->log(3, "+ cp $ext[file] $copyto");
+                $this->addFileOperation('rename', array($ext['file'], $copyto));
+                if (!OS_WINDOWS) {
+                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
+                    $this->addFileOperation('chmod', array($mode, $copyto));
+                    if (!@chmod($copyto, $mode)) {
+                        $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
+                    }
+                }
+            }
+
+
+            $data = array(
+                'role'         => $role,
+                'name'         => $bn,
+                'installed_as' => $dest,
+                'php_api'      => $ext['php_api'],
+                'zend_mod_api' => $ext['zend_mod_api'],
+                'zend_ext_api' => $ext['zend_ext_api'],
+            );
+
+            if ($filelist->getPackageXmlVersion() == '1.0') {
+                $filelist->installedFile($bn, $data);
+            } else {
+                $filelist->installedFile($bn, array('attribs' => $data));
+            }
+        }
+    }
+
+    // }}}
+    function &getUninstallPackages()
+    {
+        return $this->_downloadedPackages;
+    }
+    // {{{ 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
+     *              - register-only : update registry but don't remove files
+     *              - nodeps: do not process dependencies of other packages to ensure
+     *                        uninstallation does not break things
+     */
+    function uninstall($package, $options = array())
+    {
+        $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
+        $this->config->setInstallRoot($installRoot);
+
+        $this->installroot = '';
+        $this->_registry = &$this->config->getRegistry();
+        if (is_object($package)) {
+            $channel = $package->getChannel();
+            $pkg     = $package;
+            $package = $pkg->getPackage();
+        } else {
+            $pkg = false;
+            $info = $this->_registry->parsePackageName($package,
+                $this->config->get('default_channel'));
+            $channel = $info['channel'];
+            $package = $info['package'];
+        }
+
+        $savechannel = $this->config->get('default_channel');
+        $this->configSet('default_channel', $channel);
+        if (!is_object($pkg)) {
+            $pkg = $this->_registry->getPackage($package, $channel);
+        }
+
+        if (!$pkg) {
+            $this->configSet('default_channel', $savechannel);
+            return $this->raiseError($this->_registry->parsedPackageNameToString(
+                array(
+                    'channel' => $channel,
+                    'package' => $package
+                ), true) . ' not installed');
+        }
+
+        if ($pkg->getInstalledBinary()) {
+            // this is just an alias for a binary package
+            return $this->_registry->deletePackage($package, $channel);
+        }
+
+        $filelist = $pkg->getFilelist();
+        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
+        if (!class_exists('PEAR_Dependency2')) {
+            require_once 'PEAR/Dependency2.php';
+        }
+
+        $depchecker = &new PEAR_Dependency2($this->config, $options,
+            array('channel' => $channel, 'package' => $package),
+            PEAR_VALIDATE_UNINSTALLING);
+        $e = $depchecker->validatePackageUninstall($this);
+        PEAR::staticPopErrorHandling();
+        if (PEAR::isError($e)) {
+            if (!isset($options['ignore-errors'])) {
+                return $this->raiseError($e);
+            }
+
+            if (!isset($options['soft'])) {
+                $this->log(0, 'WARNING: ' . $e->getMessage());
+            }
+        } elseif (is_array($e)) {
+            if (!isset($options['soft'])) {
+                $this->log(0, $e[0]);
+            }
+        }
+
+        $this->pkginfo = &$pkg;
+        // pretty much nothing happens if we are only registering the uninstall
+        if (empty($options['register-only'])) {
+            // {{{ Delete the files
+            $this->startFileTransaction();
+            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
+            if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
+                PEAR::popErrorHandling();
+                $this->rollbackFileTransaction();
+                $this->configSet('default_channel', $savechannel);
+                if (!isset($options['ignore-errors'])) {
+                    return $this->raiseError($err);
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: ' . $err->getMessage());
+                }
+            } else {
+                PEAR::popErrorHandling();
+            }
+
+            if (!$this->commitFileTransaction()) {
+                $this->rollbackFileTransaction();
+                if (!isset($options['ignore-errors'])) {
+                    return $this->raiseError("uninstall failed");
+                }
+
+                if (!isset($options['soft'])) {
+                    $this->log(0, 'WARNING: uninstall failed');
+                }
+            } else {
+                $this->startFileTransaction();
+                $dirtree = $pkg->getDirTree();
+                if ($dirtree === false) {
+                    $this->configSet('default_channel', $savechannel);
+                    return $this->_registry->deletePackage($package, $channel);
+                }
+
+                // attempt to delete empty directories
+                uksort($dirtree, array($this, '_sortDirs'));
+                foreach($dirtree as $dir => $notused) {
+                    $this->addFileOperation('rmdir', array($dir));
+                }
+
+                if (!$this->commitFileTransaction()) {
+                    $this->rollbackFileTransaction();
+                    if (!isset($options['ignore-errors'])) {
+                        return $this->raiseError("uninstall failed");
+                    }
+
+                    if (!isset($options['soft'])) {
+                        $this->log(0, 'WARNING: uninstall failed');
+                    }
+                }
+            }
+            // }}}
+        }
+
+        $this->configSet('default_channel', $savechannel);
+        // Register that the package is no longer installed
+        return $this->_registry->deletePackage($package, $channel);
+    }
+
+    /**
+     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
+     *
+     * It also removes duplicate dependencies
+     * @param array an array of PEAR_PackageFile_v[1/2] objects
+     * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
+     */
+    function sortPackagesForUninstall(&$packages)
+    {
+        $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
+        if (PEAR::isError($this->_dependencyDB)) {
+            return $this->_dependencyDB;
+        }
+        usort($packages, array(&$this, '_sortUninstall'));
+    }
+
+    function _sortUninstall($a, $b)
+    {
+        if (!$a->getDeps() && !$b->getDeps()) {
+            return 0; // neither package has dependencies, order is insignificant
+        }
+        if ($a->getDeps() && !$b->getDeps()) {
+            return -1; // $a must be installed after $b because $a has dependencies
+        }
+        if (!$a->getDeps() && $b->getDeps()) {
+            return 1; // $b must be installed after $a because $b has dependencies
+        }
+        // both packages have dependencies
+        if ($this->_dependencyDB->dependsOn($a, $b)) {
+            return -1;
+        }
+        if ($this->_dependencyDB->dependsOn($b, $a)) {
+            return 1;
+        }
+        return 0;
+    }
+
+    // }}}
+    // {{{ _sortDirs()
+    function _sortDirs($a, $b)
+    {
+        if (strnatcmp($a, $b) == -1) return 1;
+        if (strnatcmp($a, $b) == 1) return -1;
+        return 0;
+    }
+
+    // }}}
+
+    // {{{ _buildCallback()
+
+    function _buildCallback($what, $data)
+    {
+        if (($what == 'cmdoutput' && $this->debug > 1) ||
+            ($what == 'output' && $this->debug > 0)) {
+            $this->ui->outputData(rtrim($data), 'build');
+        }
+    }
+
+    // }}}
+}
\ No newline at end of file