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

  1. <?php
  2. /**
  3.  * PEAR_Installer
  4.  *
  5.  * PHP versions 4 and 5
  6.  *
  7.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  8.  * that is available through the world-wide-web at the following URI:
  9.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  10.  * the PHP License and are unable to obtain it through the web, please
  11.  * send a note to license@php.net so we can mail you a copy immediately.
  12.  *
  13.  * @category   pear
  14.  * @package    PEAR
  15.  * @author     Stig Bakken <ssb@php.net>
  16.  * @author     Tomas V.V. Cox <cox@idecnet.com>
  17.  * @author     Martin Jansen <mj@php.net>
  18.  * @author     Greg Beaver <cellog@php.net>
  19.  * @copyright  1997-2008 The PHP Group
  20.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  21.  * @version    CVS: $Id: Installer.php,v 1.251 2008/01/17 05:48:37 cellog Exp $
  22.  * @link       http://pear.php.net/package/PEAR
  23.  * @since      File available since Release 0.1
  24.  */
  25.  
  26. /**
  27.  * Used for installation groups in package.xml 2.0 and platform exceptions
  28.  */
  29. require_once 'OS/Guess.php';
  30. require_once 'PEAR/Downloader.php';
  31.  
  32. define('PEAR_INSTALLER_NOBINARY', -240);
  33. /**
  34.  * Administration class used to install PEAR packages and maintain the
  35.  * installed package database.
  36.  *
  37.  * @category   pear
  38.  * @package    PEAR
  39.  * @author     Stig Bakken <ssb@php.net>
  40.  * @author     Tomas V.V. Cox <cox@idecnet.com>
  41.  * @author     Martin Jansen <mj@php.net>
  42.  * @author     Greg Beaver <cellog@php.net>
  43.  * @copyright  1997-2008 The PHP Group
  44.  * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
  45.  * @version    Release: 1.7.1
  46.  * @link       http://pear.php.net/package/PEAR
  47.  * @since      Class available since Release 0.1
  48.  */
  49. class PEAR_Installer extends PEAR_Downloader
  50. {
  51.     // {{{ properties
  52.  
  53.     /** name of the package directory, for example Foo-1.0
  54.      * @var string
  55.      */
  56.     var $pkgdir;
  57.  
  58.     /** directory where PHP code files go
  59.      * @var string
  60.      */
  61.     var $phpdir;
  62.  
  63.     /** directory where PHP extension files go
  64.      * @var string
  65.      */
  66.     var $extdir;
  67.  
  68.     /** directory where documentation goes
  69.      * @var string
  70.      */
  71.     var $docdir;
  72.  
  73.     /** installation root directory (ala PHP's INSTALL_ROOT or
  74.      * automake's DESTDIR
  75.      * @var string
  76.      */
  77.     var $installroot = '';
  78.  
  79.     /** debug level
  80.      * @var int
  81.      */
  82.     var $debug = 1;
  83.  
  84.     /** temporary directory
  85.      * @var string
  86.      */
  87.     var $tmpdir;
  88.  
  89.     /**
  90.      * PEAR_Registry object used by the installer
  91.      * @var PEAR_Registry
  92.      */
  93.     var $registry;
  94.  
  95.     /**
  96.      * array of PEAR_Downloader_Packages
  97.      * @var array
  98.      */
  99.     var $_downloadedPackages;
  100.  
  101.     /** List of file transactions queued for an install/upgrade/uninstall.
  102.      *
  103.      *  Format:
  104.      *    array(
  105.      *      0 => array("rename => array("from-file", "to-file")),
  106.      *      1 => array("delete" => array("file-to-delete")),
  107.      *      ...
  108.      *    )
  109.      *
  110.      * @var array
  111.      */
  112.     var $file_operations = array();
  113.  
  114.     // }}}
  115.  
  116.     // {{{ constructor
  117.  
  118.     /**
  119.      * PEAR_Installer constructor.
  120.      *
  121.      * @param object $ui user interface object (instance of PEAR_Frontend_*)
  122.      *
  123.      * @access public
  124.      */
  125.     function PEAR_Installer(&$ui)
  126.     {
  127.         parent::PEAR_Common();
  128.         $this->setFrontendObject($ui);
  129.         $this->debug = $this->config->get('verbose');
  130.     }
  131.  
  132.     function setOptions($options)
  133.     {
  134.         $this->_options = $options;
  135.     }
  136.  
  137.     function setConfig(&$config)
  138.     {
  139.         $this->config = &$config;
  140.         $this->_registry = &$config->getRegistry();
  141.     }
  142.  
  143.     // }}}
  144.  
  145.     function _removeBackups($files)
  146.     {
  147.         foreach ($files as $path) {
  148.             $this->addFileOperation('removebackup', array($path));
  149.         }
  150.     }
  151.  
  152.     // {{{ _deletePackageFiles()
  153.  
  154.     /**
  155.      * Delete a package's installed files, does not remove empty directories.
  156.      *
  157.      * @param string package name
  158.      * @param string channel name
  159.      * @param bool if true, then files are backed up first
  160.      * @return bool TRUE on success, or a PEAR error on failure
  161.      * @access protected
  162.      */
  163.     function _deletePackageFiles($package, $channel = false, $backup = false)
  164.     {
  165.         if (!$channel) {
  166.             $channel = 'pear.php.net';
  167.         }
  168.         if (!strlen($package)) {
  169.             return $this->raiseError("No package to uninstall given");
  170.         }
  171.         if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
  172.             // to avoid race conditions, include all possible needed files
  173.             require_once 'PEAR/Task/Common.php';
  174.             require_once 'PEAR/Task/Replace.php';
  175.             require_once 'PEAR/Task/Unixeol.php';
  176.             require_once 'PEAR/Task/Windowseol.php';
  177.             require_once 'PEAR/PackageFile/v1.php';
  178.             require_once 'PEAR/PackageFile/v2.php';
  179.             require_once 'PEAR/PackageFile/Generator/v1.php';
  180.             require_once 'PEAR/PackageFile/Generator/v2.php';
  181.         }
  182.         $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
  183.         if ($filelist == null) {
  184.             return $this->raiseError("$channel/$package not installed");
  185.         }
  186.         $ret = array();
  187.         foreach ($filelist as $file => $props) {
  188.             if (empty($props['installed_as'])) {
  189.                 continue;
  190.             }
  191.             $path = $props['installed_as'];
  192.             if ($backup) {
  193.                 $this->addFileOperation('backup', array($path));
  194.                 $ret[] = $path;
  195.             }
  196.             $this->addFileOperation('delete', array($path));
  197.         }
  198.         if ($backup) {
  199.             return $ret;
  200.         }
  201.         return true;
  202.     }
  203.  
  204.     // }}}
  205.     // {{{ _installFile()
  206.  
  207.     /**
  208.      * @param string filename
  209.      * @param array attributes from <file> tag in package.xml
  210.      * @param string path to install the file in
  211.      * @param array options from command-line
  212.      * @access private
  213.      */
  214.     function _installFile($file, $atts, $tmp_path, $options)
  215.     {
  216.         // {{{ return if this file is meant for another platform
  217.         static $os;
  218.         if (!isset($this->_registry)) {
  219.             $this->_registry = &$this->config->getRegistry();
  220.         }
  221.         if (isset($atts['platform'])) {
  222.             if (empty($os)) {
  223.                 $os = new OS_Guess();
  224.             }
  225.             if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
  226.                 $negate = true;
  227.                 $platform = substr($atts['platform'], 1);
  228.             } else {
  229.                 $negate = false;
  230.                 $platform = $atts['platform'];
  231.             }
  232.             if ((bool) $os->matchSignature($platform) === $negate) {
  233.                 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  234.                 return PEAR_INSTALLER_SKIPPED;
  235.             }
  236.         }
  237.         // }}}
  238.  
  239.         $channel = $this->pkginfo->getChannel();
  240.         // {{{ assemble the destination paths
  241.         switch ($atts['role']) {
  242.             case 'src':
  243.             case 'extsrc':
  244.                 $this->source_files++;
  245.                 return;
  246.             case 'doc':
  247.             case 'data':
  248.             case 'test':
  249.                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
  250.                             DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
  251.                 unset($atts['baseinstalldir']);
  252.                 break;
  253.             case 'ext':
  254.             case 'php':
  255.                 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
  256.                 break;
  257.             case 'script':
  258.                 $dest_dir = $this->config->get('bin_dir', null, $channel);
  259.                 break;
  260.             default:
  261.                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
  262.         }
  263.         $save_destdir = $dest_dir;
  264.         if (!empty($atts['baseinstalldir'])) {
  265.             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  266.         }
  267.         if (dirname($file) != '.' && empty($atts['install-as'])) {
  268.             $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  269.         }
  270.         if (empty($atts['install-as'])) {
  271.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  272.         } else {
  273.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  274.         }
  275.         $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  276.  
  277.         // Clean up the DIRECTORY_SEPARATOR mess
  278.         $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  279.         list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  280.                                                     array(DIRECTORY_SEPARATOR,
  281.                                                           DIRECTORY_SEPARATOR,
  282.                                                           DIRECTORY_SEPARATOR),
  283.                                                     array($dest_file, $orig_file));
  284.         $final_dest_file = $installed_as = $dest_file;
  285.         if (isset($this->_options['packagingroot'])) {
  286.             $installedas_dest_dir = dirname($final_dest_file);
  287.             $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  288.             $final_dest_file = $this->_prependPath($final_dest_file,
  289.                 $this->_options['packagingroot']);
  290.         } else {
  291.             $installedas_dest_dir = dirname($final_dest_file);
  292.             $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  293.         }
  294.         $dest_dir = dirname($final_dest_file);
  295.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  296.         if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
  297.             return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  298.         }
  299.         // }}}
  300.  
  301.         if (empty($this->_options['register-only']) &&
  302.               (!file_exists($dest_dir) || !is_dir($dest_dir))) {
  303.             if (!$this->mkDirHier($dest_dir)) {
  304.                 return $this->raiseError("failed to mkdir $dest_dir",
  305.                                          PEAR_INSTALLER_FAILED);
  306.             }
  307.             $this->log(3, "+ mkdir $dest_dir");
  308.         }
  309.         // pretty much nothing happens if we are only registering the install
  310.         if (empty($this->_options['register-only'])) {
  311.             if (empty($atts['replacements'])) {
  312.                 if (!file_exists($orig_file)) {
  313.                     return $this->raiseError("file $orig_file does not exist",
  314.                                              PEAR_INSTALLER_FAILED);
  315.                 }
  316.                 if (!@copy($orig_file, $dest_file)) {
  317.                     return $this->raiseError("failed to write $dest_file: $php_errormsg",
  318.                                              PEAR_INSTALLER_FAILED);
  319.                 }
  320.                 $this->log(3, "+ cp $orig_file $dest_file");
  321.                 if (isset($atts['md5sum'])) {
  322.                     $md5sum = md5_file($dest_file);
  323.                 }
  324.             } else {
  325.                 // {{{ file with replacements
  326.                 if (!file_exists($orig_file)) {
  327.                     return $this->raiseError("file does not exist",
  328.                                              PEAR_INSTALLER_FAILED);
  329.                 }
  330.                 $contents = file_get_contents($orig_file);
  331.                 if ($contents === false) {
  332.                     $contents = '';
  333.                 }
  334.                 if (isset($atts['md5sum'])) {
  335.                     $md5sum = md5($contents);
  336.                 }
  337.                 $subst_from = $subst_to = array();
  338.                 foreach ($atts['replacements'] as $a) {
  339.                     $to = '';
  340.                     if ($a['type'] == 'php-const') {
  341.                         if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
  342.                             eval("\$to = $a[to];");
  343.                         } else {
  344.                             if (!isset($options['soft'])) {
  345.                                 $this->log(0, "invalid php-const replacement: $a[to]");
  346.                             }
  347.                             continue;
  348.                         }
  349.                     } elseif ($a['type'] == 'pear-config') {
  350.                         if ($a['to'] == 'master_server') {
  351.                             $chan = $this->_registry->getChannel($channel);
  352.                             if (!PEAR::isError($chan)) {
  353.                                 $to = $chan->getServer();
  354.                             } else {
  355.                                 $to = $this->config->get($a['to'], null, $channel);
  356.                             }
  357.                         } else {
  358.                             $to = $this->config->get($a['to'], null, $channel);
  359.                         }
  360.                         if (is_null($to)) {
  361.                             if (!isset($options['soft'])) {
  362.                                 $this->log(0, "invalid pear-config replacement: $a[to]");
  363.                             }
  364.                             continue;
  365.                         }
  366.                     } elseif ($a['type'] == 'package-info') {
  367.                         if ($t = $this->pkginfo->packageInfo($a['to'])) {
  368.                             $to = $t;
  369.                         } else {
  370.                             if (!isset($options['soft'])) {
  371.                                 $this->log(0, "invalid package-info replacement: $a[to]");
  372.                             }
  373.                             continue;
  374.                         }
  375.                     }
  376.                     if (!is_null($to)) {
  377.                         $subst_from[] = $a['from'];
  378.                         $subst_to[] = $to;
  379.                     }
  380.                 }
  381.                 $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  382.                 if (sizeof($subst_from)) {
  383.                     $contents = str_replace($subst_from, $subst_to, $contents);
  384.                 }
  385.                 $wp = @fopen($dest_file, "wb");
  386.                 if (!is_resource($wp)) {
  387.                     return $this->raiseError("failed to create $dest_file: $php_errormsg",
  388.                                              PEAR_INSTALLER_FAILED);
  389.                 }
  390.                 if (@fwrite($wp, $contents) === false) {
  391.                     return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  392.                                              PEAR_INSTALLER_FAILED);
  393.                 }
  394.                 fclose($wp);
  395.                 // }}}
  396.             }
  397.             // {{{ check the md5
  398.             if (isset($md5sum)) {
  399.                 if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
  400.                     $this->log(2, "md5sum ok: $final_dest_file");
  401.                 } else {
  402.                     if (empty($options['force'])) {
  403.                         // delete the file
  404.                         if (file_exists($dest_file)) {
  405.                             unlink($dest_file);
  406.                         }
  407.                         if (!isset($options['ignore-errors'])) {
  408.                             return $this->raiseError("bad md5sum for file $final_dest_file",
  409.                                                  PEAR_INSTALLER_FAILED);
  410.                         } else {
  411.                             if (!isset($options['soft'])) {
  412.                                 $this->log(0, "warning : bad md5sum for file $final_dest_file");
  413.                             }
  414.                         }
  415.                     } else {
  416.                         if (!isset($options['soft'])) {
  417.                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
  418.                         }
  419.                     }
  420.                 }
  421.             }
  422.             // }}}
  423.             // {{{ set file permissions
  424.             if (!OS_WINDOWS) {
  425.                 if ($atts['role'] == 'script') {
  426.                     $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  427.                     $this->log(3, "+ chmod +x $dest_file");
  428.                 } else {
  429.                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  430.                 }
  431.                 if ($atts['role'] != 'src') {
  432.                     $this->addFileOperation("chmod", array($mode, $dest_file));
  433.                     if (!@chmod($dest_file, $mode)) {
  434.                         if (!isset($options['soft'])) {
  435.                             $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  436.                         }
  437.                     }
  438.                 }
  439.             }
  440.             // }}}
  441.             if ($atts['role'] == 'src') {
  442.                 rename($dest_file, $final_dest_file);
  443.                 $this->log(2, "renamed source file $dest_file to $final_dest_file");
  444.             } else {
  445.                 $this->addFileOperation("rename", array($dest_file, $final_dest_file,
  446.                     $atts['role'] == 'ext'));
  447.             }
  448.         }
  449.         // Store the full path where the file was installed for easy unistall
  450.         if ($atts['role'] != 'script') {
  451.             $loc = $this->config->get($atts['role'] . '_dir');
  452.         } else {
  453.             $loc = $this->config->get('bin_dir');
  454.         }
  455.         if ($atts['role'] != 'src') {
  456.             $this->addFileOperation("installed_as", array($file, $installed_as,
  457.                                     $loc,
  458.                                     dirname(substr($installedas_dest_file, strlen($loc)))));
  459.         }
  460.  
  461.         //$this->log(2, "installed: $dest_file");
  462.         return PEAR_INSTALLER_OK;
  463.     }
  464.  
  465.     // }}}
  466.     // {{{ _installFile2()
  467.  
  468.     /**
  469.      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  470.      * @param string filename
  471.      * @param array attributes from <file> tag in package.xml
  472.      * @param string path to install the file in
  473.      * @param array options from command-line
  474.      * @access private
  475.      */
  476.     function _installFile2(&$pkg, $file, $atts, $tmp_path, $options)
  477.     {
  478.         if (!isset($this->_registry)) {
  479.             $this->_registry = &$this->config->getRegistry();
  480.         }
  481.  
  482.         $channel = $pkg->getChannel();
  483.         // {{{ assemble the destination paths
  484.         if (!in_array($atts['attribs']['role'],
  485.               PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
  486.             return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
  487.                     "' for file $file");
  488.         }
  489.         $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
  490.         $err = $role->setup($this, $pkg, $atts['attribs'], $file);
  491.         if (PEAR::isError($err)) {
  492.             return $err;
  493.         }
  494.         if (!$role->isInstallable()) {
  495.             return;
  496.         }
  497.         $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
  498.         if (PEAR::isError($info)) {
  499.             return $info;
  500.         } else {
  501.             list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
  502.         }
  503.         if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
  504.             return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
  505.         }
  506.         $final_dest_file = $installed_as = $dest_file;
  507.         if (isset($this->_options['packagingroot'])) {
  508.             $final_dest_file = $this->_prependPath($final_dest_file,
  509.                 $this->_options['packagingroot']);
  510.         }
  511.         $dest_dir = dirname($final_dest_file);
  512.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  513.         // }}}
  514.  
  515.         if (empty($this->_options['register-only'])) {
  516.             if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
  517.                 if (!$this->mkDirHier($dest_dir)) {
  518.                     return $this->raiseError("failed to mkdir $dest_dir",
  519.                                              PEAR_INSTALLER_FAILED);
  520.                 }
  521.                 $this->log(3, "+ mkdir $dest_dir");
  522.             }
  523.         }
  524.         $attribs = $atts['attribs'];
  525.         unset($atts['attribs']);
  526.         // pretty much nothing happens if we are only registering the install
  527.         if (empty($this->_options['register-only'])) {
  528.             if (!count($atts)) { // no tasks
  529.                 if (!file_exists($orig_file)) {
  530.                     return $this->raiseError("file $orig_file does not exist",
  531.                                              PEAR_INSTALLER_FAILED);
  532.                 }
  533.                 if (!@copy($orig_file, $dest_file)) {
  534.                     return $this->raiseError("failed to write $dest_file: $php_errormsg",
  535.                                              PEAR_INSTALLER_FAILED);
  536.                 }
  537.                 $this->log(3, "+ cp $orig_file $dest_file");
  538.                 if (isset($attribs['md5sum'])) {
  539.                     $md5sum = md5_file($dest_file);
  540.                 }
  541.             } else { // file with tasks
  542.                 if (!file_exists($orig_file)) {
  543.                     return $this->raiseError("file $orig_file does not exist",
  544.                                              PEAR_INSTALLER_FAILED);
  545.                 }
  546.                 $contents = file_get_contents($orig_file);
  547.                 if ($contents === false) {
  548.                     $contents = '';
  549.                 }
  550.                 if (isset($attribs['md5sum'])) {
  551.                     $md5sum = md5($contents);
  552.                 }
  553.                 foreach ($atts as $tag => $raw) {
  554.                     $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), 
  555.                         array('', '_'), $tag);
  556.                     $task = "PEAR_Task_$tag";
  557.                     $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
  558.                     if (!$task->isScript()) { // scripts are only handled after installation
  559.                         $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
  560.                         $res = $task->startSession($pkg, $contents, $final_dest_file);
  561.                         if ($res === false) {
  562.                             continue; // skip this file
  563.                         }
  564.                         if (PEAR::isError($res)) {
  565.                             return $res;
  566.                         }
  567.                         $contents = $res; // save changes
  568.                     }
  569.                     $wp = @fopen($dest_file, "wb");
  570.                     if (!is_resource($wp)) {
  571.                         return $this->raiseError("failed to create $dest_file: $php_errormsg",
  572.                                                  PEAR_INSTALLER_FAILED);
  573.                     }
  574.                     if (fwrite($wp, $contents) === false) {
  575.                         return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  576.                                                  PEAR_INSTALLER_FAILED);
  577.                     }
  578.                     fclose($wp);
  579.                 }
  580.             }
  581.             // {{{ check the md5
  582.             if (isset($md5sum)) {
  583.                 if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
  584.                     $this->log(2, "md5sum ok: $final_dest_file");
  585.                 } else {
  586.                     if (empty($options['force'])) {
  587.                         // delete the file
  588.                         if (file_exists($dest_file)) {
  589.                             unlink($dest_file);
  590.                         }
  591.                         if (!isset($options['ignore-errors'])) {
  592.                             return $this->raiseError("bad md5sum for file $final_dest_file",
  593.                                                      PEAR_INSTALLER_FAILED);
  594.                         } else {
  595.                             if (!isset($options['soft'])) {
  596.                                 $this->log(0, "warning : bad md5sum for file $final_dest_file");
  597.                             }
  598.                         }
  599.                     } else {
  600.                         if (!isset($options['soft'])) {
  601.                             $this->log(0, "warning : bad md5sum for file $final_dest_file");
  602.                         }
  603.                     }
  604.                 }
  605.             }
  606.             // }}}
  607.             // {{{ set file permissions
  608.             if (!OS_WINDOWS) {
  609.                 if ($role->isExecutable()) {
  610.                     $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  611.                     $this->log(3, "+ chmod +x $dest_file");
  612.                 } else {
  613.                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  614.                 }
  615.                 if ($attribs['role'] != 'src') {
  616.                     $this->addFileOperation("chmod", array($mode, $dest_file));
  617.                     if (!@chmod($dest_file, $mode)) {
  618.                         if (!isset($options['soft'])) {
  619.                             $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
  620.                         }
  621.                     }
  622.                 }
  623.             }
  624.             // }}}
  625.             if ($attribs['role'] == 'src') {
  626.                 rename($dest_file, $final_dest_file);
  627.                 $this->log(2, "renamed source file $dest_file to $final_dest_file");
  628.             } else {
  629.                 $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
  630.             }
  631.         }
  632.         // Store the full path where the file was installed for easy uninstall
  633.         if ($attribs['role'] != 'src') {
  634.             $loc = $this->config->get($role->getLocationConfig(), null, $channel);
  635.             $this->addFileOperation("installed_as", array($file, $installed_as,
  636.                                 $loc,
  637.                                 dirname(substr($installed_as, strlen($loc)))));
  638.         }
  639.  
  640.         //$this->log(2, "installed: $dest_file");
  641.         return PEAR_INSTALLER_OK;
  642.     }
  643.  
  644.     // }}}
  645.     // {{{ addFileOperation()
  646.  
  647.     /**
  648.      * Add a file operation to the current file transaction.
  649.      *
  650.      * @see startFileTransaction()
  651.      * @param string $type This can be one of:
  652.      *    - rename:  rename a file ($data has 3 values)
  653.      *    - backup:  backup an existing file ($data has 1 value)
  654.      *    - removebackup:  clean up backups created during install ($data has 1 value)
  655.      *    - chmod:   change permissions on a file ($data has 2 values)
  656.      *    - delete:  delete a file ($data has 1 value)
  657.      *    - rmdir:   delete a directory if empty ($data has 1 value)
  658.      *    - installed_as: mark a file as installed ($data has 4 values).
  659.      * @param array $data For all file operations, this array must contain the
  660.      *    full path to the file or directory that is being operated on.  For
  661.      *    the rename command, the first parameter must be the file to rename,
  662.      *    the second its new name, the third whether this is a PHP extension.
  663.      *
  664.      *    The installed_as operation contains 4 elements in this order:
  665.      *    1. Filename as listed in the filelist element from package.xml
  666.      *    2. Full path to the installed file
  667.      *    3. Full path from the php_dir configuration variable used in this
  668.      *       installation
  669.      *    4. Relative path from the php_dir that this file is installed in
  670.      */
  671.     function addFileOperation($type, $data)
  672.     {
  673.         if (!is_array($data)) {
  674.             return $this->raiseError('Internal Error: $data in addFileOperation'
  675.                 . ' must be an array, was ' . gettype($data));
  676.         }
  677.         if ($type == 'chmod') {
  678.             $octmode = decoct($data[0]);
  679.             $this->log(3, "adding to transaction: $type $octmode $data[1]");
  680.         } else {
  681.             $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  682.         }
  683.         $this->file_operations[] = array($type, $data);
  684.     }
  685.  
  686.     // }}}
  687.     // {{{ startFileTransaction()
  688.  
  689.     function startFileTransaction($rollback_in_case = false)
  690.     {
  691.         if (count($this->file_operations) && $rollback_in_case) {
  692.             $this->rollbackFileTransaction();
  693.         }
  694.         $this->file_operations = array();
  695.     }
  696.  
  697.     // }}}
  698.     // {{{ commitFileTransaction()
  699.  
  700.     function commitFileTransaction()
  701.     {
  702.         $n = count($this->file_operations);
  703.         $this->log(2, "about to commit $n file operations");
  704.         // {{{ first, check permissions and such manually
  705.         $errors = array();
  706.         foreach ($this->file_operations as $tr) {
  707.             list($type, $data) = $tr;
  708.             switch ($type) {
  709.                 case 'rename':
  710.                     if (!file_exists($data[0])) {
  711.                         $errors[] = "cannot rename file $data[0], doesn't exist";
  712.                     }
  713.                     // check that dest dir. is writable
  714.                     if (!is_writable(dirname($data[1]))) {
  715.                         $errors[] = "permission denied ($type): $data[1]";
  716.                     }
  717.                     break;
  718.                 case 'chmod':
  719.                     // check that file is writable
  720.                     if (!is_writable($data[1])) {
  721.                         $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  722.                     }
  723.                     break;
  724.                 case 'delete':
  725.                     if (!file_exists($data[0])) {
  726.                         $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  727.                     }
  728.                     // check that directory is writable
  729.                     if (file_exists($data[0])) {
  730.                         if (!is_writable(dirname($data[0]))) {
  731.                             $errors[] = "permission denied ($type): $data[0]";
  732.                         } else {
  733.                             // make sure the file to be deleted can be opened for writing
  734.                             $fp = false;
  735.                             if (!is_dir($data[0]) &&
  736.                                   (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
  737.                                 $errors[] = "permission denied ($type): $data[0]";
  738.                             } elseif ($fp) {
  739.                                 fclose($fp);
  740.                             }
  741.                         }
  742.                     }
  743.                     break;
  744.             }
  745.  
  746.         }
  747.         // }}}
  748.         $m = sizeof($errors);
  749.         if ($m > 0) {
  750.             foreach ($errors as $error) {
  751.                 if (!isset($this->_options['soft'])) {
  752.                     $this->log(1, $error);
  753.                 }
  754.             }
  755.             if (!isset($this->_options['ignore-errors'])) {
  756.                 return false;
  757.             }
  758.         }
  759.         $this->_dirtree = array();
  760.         // {{{ really commit the transaction
  761.         foreach ($this->file_operations as $i => $tr) {
  762.             if (!$tr) {
  763.                 // support removal of non-existing backups
  764.                 continue;
  765.             }
  766.             list($type, $data) = $tr;
  767.             switch ($type) {
  768.                 case 'backup':
  769.                     if (!file_exists($data[0])) {
  770.                         $this->file_operations[$i] = false;
  771.                         break;
  772.                     }
  773.                     if (!@copy($data[0], $data[0] . '.bak')) {
  774.                         $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
  775.                             '.bak ' . $php_errormsg);
  776.                         return false;
  777.                     }
  778.                     $this->log(3, "+ backup $data[0] to $data[0].bak");
  779.                     break;
  780.                 case 'removebackup':
  781.                     if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
  782.                         unlink($data[0] . '.bak');
  783.                         $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  784.                     }
  785.                     break;
  786.                 case 'rename':
  787.                     if (file_exists($data[1])) {
  788.                         $test = @unlink($data[1]);
  789.                     } else {
  790.                         $test = null;
  791.                     }
  792.                     if (!$test && file_exists($data[1])) {
  793.                         if ($data[2]) {
  794.                             $extra = ', this extension must be installed manually.  Rename to "' .
  795.                                 basename($data[1]) . '"';
  796.                         } else {
  797.                             $extra = '';
  798.                         }
  799.                         if (!isset($this->_options['soft'])) {
  800.                             $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
  801.                                 $data[0] . $extra);
  802.                         }
  803.                         if (!isset($this->_options['ignore-errors'])) {
  804.                             return false;
  805.                         }
  806.                     }
  807.                     // permissions issues with rename - copy() is far superior
  808.                     $perms = @fileperms($data[0]);
  809.                     if (!@copy($data[0], $data[1])) {
  810.                         $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
  811.                             ' ' . $php_errormsg);
  812.                         return false;
  813.                     }
  814.                     // copy over permissions, otherwise they are lost
  815.                     @chmod($data[1], $perms);
  816.                     @unlink($data[0]);
  817.                     $this->log(3, "+ mv $data[0] $data[1]");
  818.                     break;
  819.                 case 'chmod':
  820.                     if (!@chmod($data[1], $data[0])) {
  821.                         $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
  822.                             decoct($data[0]) . ' ' . $php_errormsg);
  823.                         return false;
  824.                     }
  825.                     $octmode = decoct($data[0]);
  826.                     $this->log(3, "+ chmod $octmode $data[1]");
  827.                     break;
  828.                 case 'delete':
  829.                     if (file_exists($data[0])) {
  830.                         if (!@unlink($data[0])) {
  831.                             $this->log(1, 'Could not delete ' . $data[0] . ' ' .
  832.                                 $php_errormsg);
  833.                             return false;
  834.                         }
  835.                         $this->log(3, "+ rm $data[0]");
  836.                     }
  837.                     break;
  838.                 case 'rmdir':
  839.                     if (file_exists($data[0])) {
  840.                         do {
  841.                             $testme = opendir($data[0]);
  842.                             while (false !== ($entry = readdir($testme))) {
  843.                                 if ($entry == '.' || $entry == '..') {
  844.                                     continue;
  845.                                 }
  846.                                 closedir($testme);
  847.                                 break 2; // this directory is not empty and can't be
  848.                                          // deleted
  849.                             }
  850.                             closedir($testme);
  851.                             if (!@rmdir($data[0])) {
  852.                                 $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
  853.                                     $php_errormsg);
  854.                                 return false;
  855.                             }
  856.                             $this->log(3, "+ rmdir $data[0]");
  857.                         } while (false);
  858.                     }
  859.                     break;
  860.                 case 'installed_as':
  861.                     $this->pkginfo->setInstalledAs($data[0], $data[1]);
  862.                     if (!isset($this->_dirtree[dirname($data[1])])) {
  863.                         $this->_dirtree[dirname($data[1])] = true;
  864.                         $this->pkginfo->setDirtree(dirname($data[1]));
  865.  
  866.                         while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
  867.                                 $data[3] != '/' && $data[3] != '\\') {
  868.                             $this->pkginfo->setDirtree($pp =
  869.                                 $this->_prependPath($data[3], $data[2]));
  870.                             $this->_dirtree[$pp] = true;
  871.                             $data[3] = dirname($data[3]);
  872.                         }
  873.                     }
  874.                     break;
  875.             }
  876.         }
  877.         // }}}
  878.         $this->log(2, "successfully committed $n file operations");
  879.         $this->file_operations = array();
  880.         return true;
  881.     }
  882.  
  883.     // }}}
  884.     // {{{ rollbackFileTransaction()
  885.  
  886.     function rollbackFileTransaction()
  887.     {
  888.         $n = count($this->file_operations);
  889.         $this->log(2, "rolling back $n file operations");
  890.         foreach ($this->file_operations as $tr) {
  891.             list($type, $data) = $tr;
  892.             switch ($type) {
  893.                 case 'backup':
  894.                     if (file_exists($data[0] . '.bak')) {
  895.                         if (file_exists($data[0] && is_writable($data[0]))) {
  896.                             unlink($data[0]);
  897.                         }
  898.                         @copy($data[0] . '.bak', $data[0]);
  899.                         $this->log(3, "+ restore $data[0] from $data[0].bak");
  900.                     }
  901.                     break;
  902.                 case 'removebackup':
  903.                     if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
  904.                         unlink($data[0] . '.bak');
  905.                         $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
  906.                     }
  907.                     break;
  908.                 case 'rename':
  909.                     @unlink($data[0]);
  910.                     $this->log(3, "+ rm $data[0]");
  911.                     break;
  912.                 case 'mkdir':
  913.                     @rmdir($data[0]);
  914.                     $this->log(3, "+ rmdir $data[0]");
  915.                     break;
  916.                 case 'chmod':
  917.                     break;
  918.                 case 'delete':
  919.                     break;
  920.                 case 'installed_as':
  921.                     $this->pkginfo->setInstalledAs($data[0], false);
  922.                     break;
  923.             }
  924.         }
  925.         $this->pkginfo->resetDirtree();
  926.         $this->file_operations = array();
  927.     }
  928.  
  929.     // }}}
  930.     // {{{ mkDirHier($dir)
  931.  
  932.     function mkDirHier($dir)
  933.     {
  934.         $this->addFileOperation('mkdir', array($dir));
  935.         return parent::mkDirHier($dir);
  936.     }
  937.  
  938.     // }}}
  939.     // {{{ download()
  940.  
  941.     /**
  942.      * Download any files and their dependencies, if necessary
  943.      *
  944.      * @param array a mixed list of package names, local files, or package.xml
  945.      * @param PEAR_Config
  946.      * @param array options from the command line
  947.      * @param array this is the array that will be populated with packages to
  948.      *              install.  Format of each entry:
  949.      *
  950.      * <code>
  951.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  952.      *    'info' => array() // parsed package.xml
  953.      * );
  954.      * </code>
  955.      * @param array this will be populated with any error messages
  956.      * @param false private recursion variable
  957.      * @param false private recursion variable
  958.      * @param false private recursion variable
  959.      * @deprecated in favor of PEAR_Downloader
  960.      */
  961.     function download($packages, $options, &$config, &$installpackages,
  962.                       &$errors, $installed = false, $willinstall = false, $state = false)
  963.     {
  964.         // trickiness: initialize here
  965.         parent::PEAR_Downloader($this->ui, $options, $config);
  966.         $ret = parent::download($packages);
  967.         $errors = $this->getErrorMsgs();
  968.         $installpackages = $this->getDownloadedPackages();
  969.         trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  970.                       "in favor of PEAR_Downloader class", E_USER_WARNING);
  971.         return $ret;
  972.     }
  973.  
  974.     // }}}
  975.     // {{{ _parsePackageXml()
  976.  
  977.     function _parsePackageXml(&$descfile, &$tmpdir)
  978.     {
  979.         if (substr($descfile, -4) == '.xml') {
  980.             $tmpdir = false;
  981.         } else {
  982.             // {{{ Decompress pack in tmp dir -------------------------------------
  983.  
  984.             // To allow relative package file names
  985.             $descfile = realpath($descfile);
  986.  
  987.             if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  988.                 return $tmpdir;
  989.             }
  990.             $this->log(3, '+ tmp dir created at ' . $tmpdir);
  991.             // }}}
  992.         }
  993.         // Parse xml file -----------------------------------------------
  994.         $pkg = new PEAR_PackageFile($this->config, $this->debug, $tmpdir);
  995.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  996.         $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
  997.         PEAR::staticPopErrorHandling();
  998.         if (PEAR::isError($p)) {
  999.             if (is_array($p->getUserInfo())) {
  1000.                 foreach ($p->getUserInfo() as $err) {
  1001.                     $loglevel = $err['level'] == 'error' ? 0 : 1;
  1002.                     if (!isset($this->_options['soft'])) {
  1003.                         $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
  1004.                     }
  1005.                 }
  1006.             }
  1007.             return $this->raiseError('Installation failed: invalid package file');
  1008.         } else {
  1009.             $descfile = $p->getPackageFile();
  1010.         }
  1011.         return $p;
  1012.     }
  1013.  
  1014.     // }}}
  1015.     /**
  1016.      * Set the list of PEAR_Downloader_Package objects to allow more sane
  1017.      * dependency validation
  1018.      * @param array
  1019.      */
  1020.     function setDownloadedPackages(&$pkgs)
  1021.     {
  1022.         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  1023.         $err = $this->analyzeDependencies($pkgs);
  1024.         PEAR::popErrorHandling();
  1025.         if (PEAR::isError($err)) {
  1026.             return $err;
  1027.         }
  1028.         $this->_downloadedPackages = &$pkgs;
  1029.     }
  1030.  
  1031.     /**
  1032.      * Set the list of PEAR_Downloader_Package objects to allow more sane
  1033.      * dependency validation
  1034.      * @param array
  1035.      */
  1036.     function setUninstallPackages(&$pkgs)
  1037.     {
  1038.         $this->_downloadedPackages = &$pkgs;
  1039.     }
  1040.  
  1041.     function getInstallPackages()
  1042.     {
  1043.         return $this->_downloadedPackages;
  1044.     }
  1045.  
  1046.     // {{{ install()
  1047.  
  1048.     /**
  1049.      * Installs the files within the package file specified.
  1050.      *
  1051.      * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
  1052.      *        or a pre-initialized packagefile object
  1053.      * @param array $options
  1054.      * recognized options:
  1055.      * - installroot   : optional prefix directory for installation
  1056.      * - force         : force installation
  1057.      * - register-only : update registry but don't install files
  1058.      * - upgrade       : upgrade existing install
  1059.      * - soft          : fail silently
  1060.      * - nodeps        : ignore dependency conflicts/missing dependencies
  1061.      * - alldeps       : install all dependencies
  1062.      * - onlyreqdeps   : install only required dependencies
  1063.      *
  1064.      * @return array|PEAR_Error package info if successful
  1065.      */
  1066.  
  1067.     function install($pkgfile, $options = array())
  1068.     {
  1069.         $this->_options = $options;
  1070.         $this->_registry = &$this->config->getRegistry();
  1071.         if (is_object($pkgfile)) {
  1072.             $dlpkg = &$pkgfile;
  1073.             $pkg = $pkgfile->getPackageFile();
  1074.             $pkgfile = $pkg->getArchiveFile();
  1075.             $descfile = $pkg->getPackageFile();
  1076.             $tmpdir = dirname($descfile);
  1077.         } else {
  1078.             $descfile = $pkgfile;
  1079.             $tmpdir = '';
  1080.             if (PEAR::isError($pkg = &$this->_parsePackageXml($descfile, $tmpdir))) {
  1081.                 return $pkg;
  1082.             }
  1083.         }
  1084.  
  1085.         if (realpath($descfile) != realpath($pkgfile)) {
  1086.             $tar = new Archive_Tar($pkgfile);
  1087.             if (!$tar->extract($tmpdir)) {
  1088.                 return $this->raiseError("unable to unpack $pkgfile");
  1089.             }
  1090.         }
  1091.  
  1092.         $pkgname = $pkg->getName();
  1093.         $channel = $pkg->getChannel();
  1094.         if (isset($this->_options['packagingroot'])) {
  1095.             $regdir = $this->_prependPath(
  1096.                 $this->config->get('php_dir', null, 'pear.php.net'),
  1097.                 $this->_options['packagingroot']);
  1098.             $packrootphp_dir = $this->_prependPath(
  1099.                 $this->config->get('php_dir', null, $channel),
  1100.                 $this->_options['packagingroot']);
  1101.         }
  1102.  
  1103.         if (isset($options['installroot'])) {
  1104.             $this->config->setInstallRoot($options['installroot']);
  1105.             $this->_registry = &$this->config->getRegistry();
  1106.             $installregistry = &$this->_registry;
  1107.             $this->installroot = ''; // all done automagically now
  1108.             $php_dir = $this->config->get('php_dir', null, $channel);
  1109.         } else {
  1110.             $this->config->setInstallRoot(false);
  1111.             $this->_registry = &$this->config->getRegistry();
  1112.             if (isset($this->_options['packagingroot'])) {
  1113.                 $installregistry = &new PEAR_Registry($regdir);
  1114.                 if (!$installregistry->channelExists($channel, true)) {
  1115.                     // we need to fake a channel-discover of this channel
  1116.                     $chanobj = $this->_registry->getChannel($channel, true);
  1117.                     $installregistry->addChannel($chanobj);
  1118.                 }
  1119.                 $php_dir = $packrootphp_dir;
  1120.             } else {
  1121.                 $installregistry = &$this->_registry;
  1122.                 $php_dir = $this->config->get('php_dir', null, $channel);
  1123.             }
  1124.             $this->installroot = '';
  1125.         }
  1126.  
  1127.         // {{{ checks to do when not in "force" mode
  1128.         if (empty($options['force']) &&
  1129.               (file_exists($this->config->get('php_dir')) &&
  1130.                is_dir($this->config->get('php_dir')))) {
  1131.             $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
  1132.             $instfilelist = $pkg->getInstallationFileList(true);
  1133.             if (PEAR::isError($instfilelist)) {
  1134.                 return $instfilelist;
  1135.             }
  1136.             // ensure we have the most accurate registry
  1137.             $installregistry->flushFileMap();
  1138.             $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
  1139.             if (PEAR::isError($test)) {
  1140.                 return $test;
  1141.             }
  1142.             if (sizeof($test)) {
  1143.                 $pkgs = $this->getInstallPackages();
  1144.                 $found = false;
  1145.                 foreach ($pkgs as $param) {
  1146.                     if ($pkg->isSubpackageOf($param)) {
  1147.                         $found = true;
  1148.                         break;
  1149.                     }
  1150.                 }
  1151.                 if ($found) {
  1152.                     // subpackages can conflict with earlier versions of parent packages
  1153.                     $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
  1154.                     $tmp = $test;
  1155.                     foreach ($tmp as $file => $info) {
  1156.                         if (is_array($info)) {
  1157.                             if (strtolower($info[1]) == strtolower($param->getPackage()) &&
  1158.                                   strtolower($info[0]) == strtolower($param->getChannel())) {
  1159.                                 unset($test[$file]);
  1160.                                 unset($parentreg['filelist'][$file]);
  1161.                             }
  1162.                         } else {
  1163.                             if (strtolower($param->getChannel()) != 'pear.php.net') {
  1164.                                 continue;
  1165.                             }
  1166.                             if (strtolower($info) == strtolower($param->getPackage())) {
  1167.                                 unset($test[$file]);
  1168.                                 unset($parentreg['filelist'][$file]);
  1169.                             }
  1170.                         }
  1171.                     }
  1172.                     $pfk = &new PEAR_PackageFile($this->config);
  1173.                     $parentpkg = &$pfk->fromArray($parentreg);
  1174.                     $installregistry->updatePackage2($parentpkg);
  1175.                 }
  1176.                 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
  1177.                     $tmp = $test;
  1178.                     foreach ($tmp as $file => $info) {
  1179.                         if (is_string($info)) {
  1180.                             // pear.php.net packages are always stored as strings
  1181.                             if (strtolower($info) == strtolower($param->getPackage())) {
  1182.                                 // upgrading existing package
  1183.                                 unset($test[$file]);
  1184.                             }
  1185.                         }
  1186.                     }
  1187.                 }
  1188.                 if (sizeof($test)) {
  1189.                     $msg = "$channel/$pkgname: conflicting files found:\n";
  1190.                     $longest = max(array_map("strlen", array_keys($test)));
  1191.                     $fmt = "%${longest}s (%s)\n";
  1192.                     foreach ($test as $file => $info) {
  1193.                         if (!is_array($info)) {
  1194.                             $info = array('pear.php.net', $info);
  1195.                         }
  1196.                         $info = $info[0] . '/' . $info[1];
  1197.                         $msg .= sprintf($fmt, $file, $info);
  1198.                     }
  1199.                     if (!isset($options['ignore-errors'])) {
  1200.                         return $this->raiseError($msg);
  1201.                     } else {
  1202.                         if (!isset($options['soft'])) {
  1203.                             $this->log(0, "WARNING: $msg");
  1204.                         }
  1205.                     }
  1206.                 }
  1207.             }
  1208.         }
  1209.         // }}}
  1210.  
  1211.         $this->startFileTransaction();
  1212.  
  1213.         if (empty($options['upgrade']) && empty($options['soft'])) {
  1214.             // checks to do only when installing new packages
  1215.             if ($channel == 'pecl.php.net') {
  1216.                 $test = $installregistry->packageExists($pkgname, $channel);
  1217.                 if (!$test) {
  1218.                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1219.                 }
  1220.             } else {
  1221.                 $test = $installregistry->packageExists($pkgname, $channel);
  1222.             }
  1223.             if (empty($options['force']) && $test) {
  1224.                 return $this->raiseError("$channel/$pkgname is already installed");
  1225.             }
  1226.         } else {
  1227.             $usechannel = $channel;
  1228.             if ($channel == 'pecl.php.net') {
  1229.                 $test = $installregistry->packageExists($pkgname, $channel);
  1230.                 if (!$test) {
  1231.                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1232.                     $usechannel = 'pear.php.net';
  1233.                 }
  1234.             } else {
  1235.                 $test = $installregistry->packageExists($pkgname, $channel);
  1236.             }
  1237.             if ($test) {
  1238.                 $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
  1239.                 $v2 = $pkg->getVersion();
  1240.                 $cmp = version_compare("$v1", "$v2", 'gt');
  1241.                 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  1242.                     return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  1243.                 }
  1244.                 if (empty($options['register-only'])) {
  1245.                     // when upgrading, remove old release's files first:
  1246.                     if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
  1247.                           true))) {
  1248.                         if (!isset($options['ignore-errors'])) {
  1249.                             return $this->raiseError($err);
  1250.                         } else {
  1251.                             if (!isset($options['soft'])) {
  1252.                                 $this->log(0, 'WARNING: ' . $err->getMessage());
  1253.                             }
  1254.                         }
  1255.                     } else {
  1256.                         $backedup = $err;
  1257.                     }
  1258.                 }
  1259.             }
  1260.         }
  1261.  
  1262.         // {{{ Copy files to dest dir ---------------------------------------
  1263.  
  1264.         // info from the package it self we want to access from _installFile
  1265.         $this->pkginfo = &$pkg;
  1266.         // used to determine whether we should build any C code
  1267.         $this->source_files = 0;
  1268.  
  1269.         $savechannel = $this->config->get('default_channel');
  1270.         if (empty($options['register-only']) && !is_dir($php_dir)) {
  1271.             if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
  1272.                 return $this->raiseError("no installation destination directory '$php_dir'\n");
  1273.             }
  1274.         }
  1275.  
  1276.         $tmp_path = dirname($descfile);
  1277.         if (substr($pkgfile, -4) != '.xml') {
  1278.             $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
  1279.         }
  1280.  
  1281.         $this->configSet('default_channel', $channel);
  1282.         // {{{ install files
  1283.  
  1284.         $ver = $pkg->getPackagexmlVersion();
  1285.         if (version_compare($ver, '2.0', '>=')) {
  1286.             $filelist = $pkg->getInstallationFilelist();
  1287.         } else {
  1288.             $filelist = $pkg->getFileList();
  1289.         }
  1290.         if (PEAR::isError($filelist)) {
  1291.             return $filelist;
  1292.         }
  1293.         $pkg->resetFilelist();
  1294.         $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
  1295.             'version', $pkg->getChannel()));
  1296.         foreach ($filelist as $file => $atts) {
  1297.             if ($pkg->getPackagexmlVersion() == '1.0') {
  1298.                 $this->expectError(PEAR_INSTALLER_FAILED);
  1299.                 $res = $this->_installFile($file, $atts, $tmp_path, $options);
  1300.                 $this->popExpect();
  1301.             } else {
  1302.                 $this->expectError(PEAR_INSTALLER_FAILED);
  1303.                 $res = $this->_installFile2($pkg, $file, $atts, $tmp_path, $options);
  1304.                 $this->popExpect();
  1305.             }
  1306.             if (PEAR::isError($res)) {
  1307.                 if (empty($options['ignore-errors'])) {
  1308.                     $this->rollbackFileTransaction();
  1309.                     if ($res->getMessage() == "file does not exist") {
  1310.                         $this->raiseError("file $file in package.xml does not exist");
  1311.                     }
  1312.                     return $this->raiseError($res);
  1313.                 } else {
  1314.                     if (!isset($options['soft'])) {
  1315.                         $this->log(0, "Warning: " . $res->getMessage());
  1316.                     }
  1317.                 }
  1318.             }
  1319.             $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
  1320.             if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
  1321.                 // Register files that were installed
  1322.                 $pkg->installedFile($file, $atts);
  1323.             }
  1324.         }
  1325.         // }}}
  1326.  
  1327.         // {{{ compile and install source files
  1328.         if ($this->source_files > 0 && empty($options['nobuild'])) {
  1329.             if (PEAR::isError($err =
  1330.                   $this->_compileSourceFiles($savechannel, $pkg))) {
  1331.                 return $err;
  1332.             }
  1333.         }
  1334.         // }}}
  1335.  
  1336.         if (isset($backedup)) {
  1337.             $this->_removeBackups($backedup);
  1338.         }
  1339.         if (!$this->commitFileTransaction()) {
  1340.             $this->rollbackFileTransaction();
  1341.             $this->configSet('default_channel', $savechannel);
  1342.             return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  1343.         }
  1344.         // }}}
  1345.  
  1346.         $ret = false;
  1347.         $installphase = 'install';
  1348.         $oldversion = false;
  1349.         // {{{ Register that the package is installed -----------------------
  1350.         if (empty($options['upgrade'])) {
  1351.             // if 'force' is used, replace the info in registry
  1352.             $usechannel = $channel;
  1353.             if ($channel == 'pecl.php.net') {
  1354.                 $test = $installregistry->packageExists($pkgname, $channel);
  1355.                 if (!$test) {
  1356.                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1357.                     $usechannel = 'pear.php.net';
  1358.                 }
  1359.             } else {
  1360.                 $test = $installregistry->packageExists($pkgname, $channel);
  1361.             }
  1362.             if (!empty($options['force']) && $test) {
  1363.                 $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
  1364.                 $installregistry->deletePackage($pkgname, $usechannel);
  1365.             }
  1366.             $ret = $installregistry->addPackage2($pkg);
  1367.         } else {
  1368.             $usechannel = $channel;
  1369.             if ($channel == 'pecl.php.net') {
  1370.                 $test = $installregistry->packageExists($pkgname, $channel);
  1371.                 if (!$test) {
  1372.                     $test = $installregistry->packageExists($pkgname, 'pear.php.net');
  1373.                     $usechannel = 'pear.php.net';
  1374.                 }
  1375.             } else {
  1376.                 $test = $installregistry->packageExists($pkgname, $channel);
  1377.             }
  1378.             // new: upgrade installs a package if it isn't installed
  1379.             if (!$test) {
  1380.                 $ret = $installregistry->addPackage2($pkg);
  1381.             } else {
  1382.                 if ($usechannel != $channel) {
  1383.                     $installregistry->deletePackage($pkgname, $usechannel);
  1384.                     $ret = $installregistry->addPackage2($pkg);
  1385.                 } else {
  1386.                     $ret = $installregistry->updatePackage2($pkg);
  1387.                 }
  1388.                 $installphase = 'upgrade';
  1389.             }
  1390.         }
  1391.         if (!$ret) {
  1392.             $this->configSet('default_channel', $savechannel);
  1393.             return $this->raiseError("Adding package $channel/$pkgname to registry failed");
  1394.         }
  1395.         // }}}
  1396.         $this->configSet('default_channel', $savechannel);
  1397.         if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
  1398.             if (PEAR_Task_Common::hasPostinstallTasks()) {
  1399.                 PEAR_Task_Common::runPostinstallTasks($installphase);
  1400.             }
  1401.         }
  1402.         return $pkg->toArray(true);
  1403.     }
  1404.  
  1405.     // }}}
  1406.  
  1407.     // {{{ _compileSourceFiles()
  1408.     /**
  1409.      * @param string
  1410.      * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
  1411.      */
  1412.     function _compileSourceFiles($savechannel, &$filelist)
  1413.     {
  1414.         require_once 'PEAR/Builder.php';
  1415.         $this->log(1, "$this->source_files source files, building");
  1416.         $bob = &new PEAR_Builder($this->ui);
  1417.         $bob->debug = $this->debug;
  1418.         $built = $bob->build($filelist, array(&$this, '_buildCallback'));
  1419.         if (PEAR::isError($built)) {
  1420.             $this->rollbackFileTransaction();
  1421.             $this->configSet('default_channel', $savechannel);
  1422.             return $built;
  1423.         }
  1424.         $this->log(1, "\nBuild process completed successfully");
  1425.         foreach ($built as $ext) {
  1426.             $bn = basename($ext['file']);
  1427.             list($_ext_name, $_ext_suff) = explode('.', $bn);
  1428.             if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
  1429.                 if (extension_loaded($_ext_name)) {
  1430.                     $this->raiseError("Extension '$_ext_name' already loaded. " .
  1431.                                       'Please unload it in your php.ini file ' .
  1432.                                       'prior to install or upgrade');
  1433.                 }
  1434.                 $role = 'ext';
  1435.             } else {
  1436.                 $role = 'src';
  1437.             }
  1438.             $dest = $ext['dest'];
  1439.             $packagingroot = '';
  1440.             if (isset($this->_options['packagingroot'])) {
  1441.                 $packagingroot = $this->_options['packagingroot'];
  1442.             }
  1443.             $copyto = $this->_prependPath($dest, $packagingroot);
  1444.             if ($copyto != $dest) {
  1445.                 $this->log(1, "Installing '$dest' as '$copyto'");
  1446.             } else {
  1447.                 $this->log(1, "Installing '$dest'");
  1448.             }
  1449.             $copydir = dirname($copyto);
  1450.             // pretty much nothing happens if we are only registering the install
  1451.             if (empty($this->_options['register-only'])) {
  1452.                 if (!file_exists($copydir) || !is_dir($copydir)) {
  1453.                     if (!$this->mkDirHier($copydir)) {
  1454.                         return $this->raiseError("failed to mkdir $copydir",
  1455.                             PEAR_INSTALLER_FAILED);
  1456.                     }
  1457.                     $this->log(3, "+ mkdir $copydir");
  1458.                 }
  1459.                 if (!@copy($ext['file'], $copyto)) {
  1460.                     return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
  1461.                 }
  1462.                 $this->log(3, "+ cp $ext[file] $copyto");
  1463.                 $this->addFileOperation('rename', array($ext['file'], $copyto));
  1464.                 if (!OS_WINDOWS) {
  1465.                     $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  1466.                     $this->addFileOperation('chmod', array($mode, $copyto));
  1467.                     if (!@chmod($copyto, $mode)) {
  1468.                         $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
  1469.                     }
  1470.                 }
  1471.             }
  1472.  
  1473.             if ($filelist->getPackageXmlVersion() == '1.0') {
  1474.                 $filelist->installedFile($bn, array(
  1475.                     'role' => $role,
  1476.                     'name' => $bn,
  1477.                     'installed_as' => $dest,
  1478.                     'php_api' => $ext['php_api'],
  1479.                     'zend_mod_api' => $ext['zend_mod_api'],
  1480.                     'zend_ext_api' => $ext['zend_ext_api'],
  1481.                     ));
  1482.             } else {
  1483.                 $filelist->installedFile($bn, array('attribs' => array(
  1484.                     'role' => $role,
  1485.                     'name' => $bn,
  1486.                     'installed_as' => $dest,
  1487.                     'php_api' => $ext['php_api'],
  1488.                     'zend_mod_api' => $ext['zend_mod_api'],
  1489.                     'zend_ext_api' => $ext['zend_ext_api'],
  1490.                     )));
  1491.             }
  1492.         }
  1493.     }
  1494.  
  1495.     // }}}
  1496.     function &getUninstallPackages()
  1497.     {
  1498.         return $this->_downloadedPackages;
  1499.     }
  1500.     // {{{ uninstall()
  1501.  
  1502.     /**
  1503.      * Uninstall a package
  1504.      *
  1505.      * This method removes all files installed by the application, and then
  1506.      * removes any empty directories.
  1507.      * @param string package name
  1508.      * @param array Command-line options.  Possibilities include:
  1509.      *
  1510.      *              - installroot: base installation dir, if not the default
  1511.      *              - register-only : update registry but don't remove files
  1512.      *              - nodeps: do not process dependencies of other packages to ensure
  1513.      *                        uninstallation does not break things
  1514.      */
  1515.     function uninstall($package, $options = array())
  1516.     {
  1517.         if (isset($options['installroot'])) {
  1518.             $this->config->setInstallRoot($options['installroot']);
  1519.             $this->installroot = '';
  1520.         } else {
  1521.             $this->config->setInstallRoot('');
  1522.             $this->installroot = '';
  1523.         }
  1524.         $this->_registry = &$this->config->getRegistry();
  1525.         if (is_object($package)) {
  1526.             $channel = $package->getChannel();
  1527.             $pkg = $package;
  1528.             $package = $pkg->getPackage();
  1529.         } else {
  1530.             $pkg = false;
  1531.             $info = $this->_registry->parsePackageName($package,
  1532.                 $this->config->get('default_channel'));
  1533.             $channel = $info['channel'];
  1534.             $package = $info['package'];
  1535.         }
  1536.         $savechannel = $this->config->get('default_channel');
  1537.         $this->configSet('default_channel', $channel);
  1538.         if (!is_object($pkg)) {
  1539.             $pkg = $this->_registry->getPackage($package, $channel);
  1540.         }
  1541.         if (!$pkg) {
  1542.             $this->configSet('default_channel', $savechannel);
  1543.             return $this->raiseError($this->_registry->parsedPackageNameToString(
  1544.                 array(
  1545.                     'channel' => $channel,
  1546.                     'package' => $package
  1547.                 ), true) . ' not installed');
  1548.         }
  1549.         if ($pkg->getInstalledBinary()) {
  1550.             // this is just an alias for a binary package
  1551.             return $this->_registry->deletePackage($package, $channel);
  1552.         }
  1553.         $filelist = $pkg->getFilelist();
  1554.         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
  1555.         if (!class_exists('PEAR_Dependency2')) {
  1556.             require_once 'PEAR/Dependency2.php';
  1557.         }
  1558.         $depchecker = &new PEAR_Dependency2($this->config, $options, 
  1559.             array('channel' => $channel, 'package' => $package),
  1560.             PEAR_VALIDATE_UNINSTALLING);
  1561.         $e = $depchecker->validatePackageUninstall($this);
  1562.         PEAR::staticPopErrorHandling();
  1563.         if (PEAR::isError($e)) {
  1564.             if (!isset($options['ignore-errors'])) {
  1565.                 return $this->raiseError($e);
  1566.             } else {
  1567.                 if (!isset($options['soft'])) {
  1568.                     $this->log(0, 'WARNING: ' . $e->getMessage());
  1569.                 }
  1570.             }
  1571.         } elseif (is_array($e)) {
  1572.             if (!isset($options['soft'])) {
  1573.                 $this->log(0, $e[0]);
  1574.             }
  1575.         }
  1576.         $this->pkginfo = &$pkg;
  1577.         // pretty much nothing happens if we are only registering the uninstall
  1578.         if (empty($options['register-only'])) {
  1579.             // {{{ Delete the files
  1580.             $this->startFileTransaction();
  1581.             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  1582.             if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
  1583.                 PEAR::popErrorHandling();
  1584.                 $this->rollbackFileTransaction();
  1585.                 $this->configSet('default_channel', $savechannel);
  1586.                 if (!isset($options['ignore-errors'])) {
  1587.                     return $this->raiseError($err);
  1588.                 } else {
  1589.                     if (!isset($options['soft'])) {
  1590.                         $this->log(0, 'WARNING: ' . $err->getMessage());
  1591.                     }
  1592.                 }
  1593.             } else {
  1594.                 PEAR::popErrorHandling();
  1595.             }
  1596.             if (!$this->commitFileTransaction()) {
  1597.                 $this->rollbackFileTransaction();
  1598.                 if (!isset($options['ignore-errors'])) {
  1599.                     return $this->raiseError("uninstall failed");
  1600.                 } elseif (!isset($options['soft'])) {
  1601.                     $this->log(0, 'WARNING: uninstall failed');
  1602.                 }
  1603.             } else {
  1604.                 $this->startFileTransaction();
  1605.                 if ($dirtree = $pkg->getDirTree()) {
  1606.                     // attempt to delete empty directories
  1607.                     uksort($dirtree, array($this, '_sortDirs'));
  1608.                     foreach($dirtree as $dir => $notused) {
  1609.                         $this->addFileOperation('rmdir', array($dir));
  1610.                     }
  1611.                 } else {
  1612.                     $this->configSet('default_channel', $savechannel);
  1613.                     return $this->_registry->deletePackage($package, $channel);
  1614.                 }
  1615.                 if (!$this->commitFileTransaction()) {
  1616.                     $this->rollbackFileTransaction();
  1617.                     if (!isset($options['ignore-errors'])) {
  1618.                         return $this->raiseError("uninstall failed");
  1619.                     } elseif (!isset($options['soft'])) {
  1620.                         $this->log(0, 'WARNING: uninstall failed');
  1621.                     }
  1622.                 }
  1623.             }
  1624.             // }}}
  1625.         }
  1626.  
  1627.         $this->configSet('default_channel', $savechannel);
  1628.         // Register that the package is no longer installed
  1629.         return $this->_registry->deletePackage($package, $channel);
  1630.     }
  1631.  
  1632.     /**
  1633.      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
  1634.      *
  1635.      * It also removes duplicate dependencies
  1636.      * @param array an array of PEAR_PackageFile_v[1/2] objects
  1637.      * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
  1638.      */
  1639.     function sortPackagesForUninstall(&$packages)
  1640.     {
  1641.         $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
  1642.         if (PEAR::isError($this->_dependencyDB)) {
  1643.             return $this->_dependencyDB;
  1644.         }
  1645.         usort($packages, array(&$this, '_sortUninstall'));
  1646.     }
  1647.  
  1648.     function _sortUninstall($a, $b)
  1649.     {
  1650.         if (!$a->getDeps() && !$b->getDeps()) {
  1651.             return 0; // neither package has dependencies, order is insignificant
  1652.         }
  1653.         if ($a->getDeps() && !$b->getDeps()) {
  1654.             return -1; // $a must be installed after $b because $a has dependencies
  1655.         }
  1656.         if (!$a->getDeps() && $b->getDeps()) {
  1657.             return 1; // $b must be installed after $a because $b has dependencies
  1658.         }
  1659.         // both packages have dependencies
  1660.         if ($this->_dependencyDB->dependsOn($a, $b)) {
  1661.             return -1;
  1662.         }
  1663.         if ($this->_dependencyDB->dependsOn($b, $a)) {
  1664.             return 1;
  1665.         }
  1666.         return 0;
  1667.     }
  1668.  
  1669.     // }}}
  1670.     // {{{ _sortDirs()
  1671.     function _sortDirs($a, $b)
  1672.     {
  1673.         if (strnatcmp($a, $b) == -1) return 1;
  1674.         if (strnatcmp($a, $b) == 1) return -1;
  1675.         return 0;
  1676.     }
  1677.  
  1678.     // }}}
  1679.  
  1680.     // {{{ _buildCallback()
  1681.  
  1682.     function _buildCallback($what, $data)
  1683.     {
  1684.         if (($what == 'cmdoutput' && $this->debug > 1) ||
  1685.             ($what == 'output' && $this->debug > 0)) {
  1686.             $this->ui->outputData(rtrim($data), 'build');
  1687.         }
  1688.     }
  1689.  
  1690.     // }}}
  1691. }
  1692.  
  1693. // {{{ md5_file() utility function
  1694. if (!function_exists("md5_file")) {
  1695.     function md5_file($filename) {
  1696.         if (!$fd = @fopen($file, 'r')) {
  1697.             return false;
  1698.         }
  1699.         fclose($fd);
  1700.         return md5(file_get_contents($filename));
  1701.     }
  1702. }
  1703. // }}}
  1704.  
  1705. ?>