home *** CD-ROM | disk | FTP | other *** search
/ Enter 2004 June / ENTER.ISO / files / xampp-win32-1.4.5-installer.exe / xampp / Installer.php < prev    next >
Encoding:
PHP Script  |  2004-03-24  |  39.7 KB  |  1,064 lines

  1. <?php
  2. //
  3. // +----------------------------------------------------------------------+
  4. // | PHP Version 5                                                        |
  5. // +----------------------------------------------------------------------+
  6. // | Copyright (c) 1997-2004 The PHP Group                                |
  7. // +----------------------------------------------------------------------+
  8. // | This source file is subject to version 3.0 of the PHP license,       |
  9. // | that is bundled with this package in the file LICENSE, and is        |
  10. // | available through the world-wide-web at the following url:           |
  11. // | http://www.php.net/license/3_0.txt.                                  |
  12. // | If you did not receive a copy of the PHP license and are unable to   |
  13. // | obtain it through the world-wide-web, please send a note to          |
  14. // | license@php.net so we can mail you a copy immediately.               |
  15. // +----------------------------------------------------------------------+
  16. // | Authors: Stig Bakken <ssb@php.net>                                   |
  17. // |          Tomas V.V.Cox <cox@idecnet.com>                             |
  18. // |          Martin Jansen <mj@php.net>                                  |
  19. // +----------------------------------------------------------------------+
  20. //
  21. // $Id: Installer.php,v 1.147 2004/01/08 17:33:12 sniper Exp $
  22.  
  23. require_once 'PEAR/Common.php';
  24. require_once 'PEAR/Registry.php';
  25. require_once 'PEAR/Dependency.php';
  26. require_once 'PEAR/Downloader.php';
  27. require_once 'System.php';
  28.  
  29. define('PEAR_INSTALLER_OK',       1);
  30. define('PEAR_INSTALLER_FAILED',   0);
  31. define('PEAR_INSTALLER_SKIPPED', -1);
  32. define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
  33.  
  34. /**
  35.  * Administration class used to install PEAR packages and maintain the
  36.  * installed package database.
  37.  *
  38.  * TODO:
  39.  *   - Check dependencies break on package uninstall (when no force given)
  40.  *   - add a guessInstallDest() method with the code from _installFile() and
  41.  *     use that method in Registry::_rebuildFileMap() & Command_Registry::doList(),
  42.  *     others..
  43.  *
  44.  * @since PHP 4.0.2
  45.  * @author Stig Bakken <ssb@php.net>
  46.  * @author Martin Jansen <mj@php.net>
  47.  * @author Greg Beaver <cellog@php.net>
  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.     /** PEAR_Registry object used by the installer
  90.      * @var object
  91.      */
  92.     var $registry;
  93.  
  94.     /** List of file transactions queued for an install/upgrade/uninstall.
  95.      *
  96.      *  Format:
  97.      *    array(
  98.      *      0 => array("rename => array("from-file", "to-file")),
  99.      *      1 => array("delete" => array("file-to-delete")),
  100.      *      ...
  101.      *    )
  102.      *
  103.      * @var array
  104.      */
  105.     var $file_operations = array();
  106.  
  107.     // }}}
  108.  
  109.     // {{{ constructor
  110.  
  111.     /**
  112.      * PEAR_Installer constructor.
  113.      *
  114.      * @param object $ui user interface object (instance of PEAR_Frontend_*)
  115.      *
  116.      * @access public
  117.      */
  118.     function PEAR_Installer(&$ui)
  119.     {
  120.         parent::PEAR_Common();
  121.         $this->setFrontendObject($ui);
  122.         $this->debug = $this->config->get('verbose');
  123.         //$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  124.     }
  125.  
  126.     // }}}
  127.  
  128.     // {{{ _deletePackageFiles()
  129.  
  130.     /**
  131.      * Delete a package's installed files, does not remove empty directories.
  132.      *
  133.      * @param string $package package name
  134.      *
  135.      * @return bool TRUE on success, or a PEAR error on failure
  136.      *
  137.      * @access private
  138.      */
  139.     function _deletePackageFiles($package)
  140.     {
  141.         if (!strlen($package)) {
  142.             return $this->raiseError("No package to uninstall given");
  143.         }
  144.         $filelist = $this->registry->packageInfo($package, 'filelist');
  145.         if ($filelist == null) {
  146.             return $this->raiseError("$package not installed");
  147.         }
  148.         foreach ($filelist as $file => $props) {
  149.             if (empty($props['installed_as'])) {
  150.                 continue;
  151.             }
  152.             $path = $this->_prependPath($props['installed_as'], $this->installroot);
  153.             $this->addFileOperation('delete', array($path));
  154.         }
  155.         return true;
  156.     }
  157.  
  158.     // }}}
  159.     // {{{ _installFile()
  160.  
  161.     /**
  162.      * @param string filename
  163.      * @param array attributes from <file> tag in package.xml
  164.      * @param string path to install the file in
  165.      * @param array options from command-line
  166.      * @access private
  167.      */
  168.     function _installFile($file, $atts, $tmp_path, $options)
  169.     {
  170.         // {{{ return if this file is meant for another platform
  171.         static $os;
  172.         if (isset($atts['platform'])) {
  173.             if (empty($os)) {
  174.                 include_once "OS/Guess.php";
  175.                 $os = new OS_Guess();
  176.             }
  177.             if (!$os->matchSignature($atts['platform'])) {
  178.                 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
  179.                 return PEAR_INSTALLER_SKIPPED;
  180.             }
  181.         }
  182.         // }}}
  183.  
  184.         // {{{ assemble the destination paths
  185.         switch ($atts['role']) {
  186.             case 'doc':
  187.             case 'data':
  188.             case 'test':
  189.                 $dest_dir = $this->config->get($atts['role'] . '_dir') .
  190.                             DIRECTORY_SEPARATOR . $this->pkginfo['package'];
  191.                 unset($atts['baseinstalldir']);
  192.                 break;
  193.             case 'ext':
  194.             case 'php':
  195.                 $dest_dir = $this->config->get($atts['role'] . '_dir');
  196.                 break;
  197.             case 'script':
  198.                 $dest_dir = $this->config->get('bin_dir');
  199.                 break;
  200.             case 'src':
  201.             case 'extsrc':
  202.                 $this->source_files++;
  203.                 return;
  204.             default:
  205.                 return $this->raiseError("Invalid role `$atts[role]' for file $file");
  206.         }
  207.         $save_destdir = $dest_dir;
  208.         if (!empty($atts['baseinstalldir'])) {
  209.             $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
  210.         }
  211.         if (dirname($file) != '.' && empty($atts['install-as'])) {
  212.             $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
  213.         }
  214.         if (empty($atts['install-as'])) {
  215.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
  216.         } else {
  217.             $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
  218.         }
  219.         $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
  220.  
  221.         // Clean up the DIRECTORY_SEPARATOR mess
  222.         $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
  223.         list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
  224.                                                     DIRECTORY_SEPARATOR,
  225.                                                     array($dest_file, $orig_file));
  226.         $installed_as = $dest_file;
  227.         $final_dest_file = $this->_prependPath($dest_file, $this->installroot);
  228.         $dest_dir = dirname($final_dest_file);
  229.         $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
  230.         // }}}
  231.  
  232.         if (!@is_dir($dest_dir)) {
  233.             if (!$this->mkDirHier($dest_dir)) {
  234.                 return $this->raiseError("failed to mkdir $dest_dir",
  235.                                          PEAR_INSTALLER_FAILED);
  236.             }
  237.             $this->log(3, "+ mkdir $dest_dir");
  238.         }
  239.         if (empty($atts['replacements'])) {
  240.             if (!file_exists($orig_file)) {
  241.                 return $this->raiseError("file does not exist",
  242.                                          PEAR_INSTALLER_FAILED);
  243.             }
  244.             if (!@copy($orig_file, $dest_file)) {
  245.                 return $this->raiseError("failed to write $dest_file",
  246.                                          PEAR_INSTALLER_FAILED);
  247.             }
  248.             $this->log(3, "+ cp $orig_file $dest_file");
  249.             if (isset($atts['md5sum'])) {
  250.                 $md5sum = md5_file($dest_file);
  251.             }
  252.         } else {
  253.             // {{{ file with replacements
  254.             if (!file_exists($orig_file)) {
  255.                 return $this->raiseError("file does not exist",
  256.                                          PEAR_INSTALLER_FAILED);
  257.             }
  258.             $fp = fopen($orig_file, "r");
  259.             $contents = fread($fp, filesize($orig_file));
  260.             fclose($fp);
  261.             if (isset($atts['md5sum'])) {
  262.                 $md5sum = md5($contents);
  263.             }
  264.             $subst_from = $subst_to = array();
  265.             foreach ($atts['replacements'] as $a) {
  266.                 $to = '';
  267.                 if ($a['type'] == 'php-const') {
  268.                     if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) {
  269.                         eval("\$to = $a[to];");
  270.                     } else {
  271.                         $this->log(0, "invalid php-const replacement: $a[to]");
  272.                         continue;
  273.                     }
  274.                 } elseif ($a['type'] == 'pear-config') {
  275.                     $to = $this->config->get($a['to']);
  276.                     if (is_null($to)) {
  277.                         $this->log(0, "invalid pear-config replacement: $a[to]");
  278.                         continue;
  279.                     }
  280.                 } elseif ($a['type'] == 'package-info') {
  281.                     if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) {
  282.                         $to = $this->pkginfo[$a['to']];
  283.                     } else {
  284.                         $this->log(0, "invalid package-info replacement: $a[to]");
  285.                         continue;
  286.                     }
  287.                 }
  288.                 if (!is_null($to)) {
  289.                     $subst_from[] = $a['from'];
  290.                     $subst_to[] = $to;
  291.                 }
  292.             }
  293.             $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
  294.             if (sizeof($subst_from)) {
  295.                 $contents = str_replace($subst_from, $subst_to, $contents);
  296.             }
  297.             $wp = @fopen($dest_file, "wb");
  298.             if (!is_resource($wp)) {
  299.                 return $this->raiseError("failed to create $dest_file: $php_errormsg",
  300.                                          PEAR_INSTALLER_FAILED);
  301.             }
  302.             if (!fwrite($wp, $contents)) {
  303.                 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
  304.                                          PEAR_INSTALLER_FAILED);
  305.             }
  306.             fclose($wp);
  307.             // }}}
  308.         }
  309.         // {{{ check the md5
  310.         if (isset($md5sum)) {
  311.             if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
  312.                 $this->log(2, "md5sum ok: $final_dest_file");
  313.             } else {
  314.                 if (empty($options['force'])) {
  315.                     // delete the file
  316.                     @unlink($dest_file);
  317.                     return $this->raiseError("bad md5sum for file $final_dest_file",
  318.                                              PEAR_INSTALLER_FAILED);
  319.                 } else {
  320.                     $this->log(0, "warning : bad md5sum for file $final_dest_file");
  321.                 }
  322.             }
  323.         }
  324.         // }}}
  325.         // {{{ set file permissions
  326.         if (!OS_WINDOWS) {
  327.             if ($atts['role'] == 'script') {
  328.                 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
  329.                 $this->log(3, "+ chmod +x $dest_file");
  330.             } else {
  331.                 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
  332.             }
  333.             $this->addFileOperation("chmod", array($mode, $dest_file));
  334.             if (!@chmod($dest_file, $mode)) {
  335.                 $this->log(0, "failed to change mode of $dest_file");
  336.             }
  337.         }
  338.         // }}}
  339.         $this->addFileOperation("rename", array($dest_file, $final_dest_file));
  340.         // Store the full path where the file was installed for easy unistall
  341.         $this->addFileOperation("installed_as", array($file, $installed_as,
  342.                                 $save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
  343.  
  344.         //$this->log(2, "installed: $dest_file");
  345.         return PEAR_INSTALLER_OK;
  346.     }
  347.  
  348.     // }}}
  349.     // {{{ addFileOperation()
  350.  
  351.     /**
  352.      * Add a file operation to the current file transaction.
  353.      *
  354.      * @see startFileTransaction()
  355.      * @var string $type This can be one of:
  356.      *    - rename:  rename a file ($data has 2 values)
  357.      *    - chmod:   change permissions on a file ($data has 2 values)
  358.      *    - delete:  delete a file ($data has 1 value)
  359.      *    - rmdir:   delete a directory if empty ($data has 1 value)
  360.      *    - installed_as: mark a file as installed ($data has 4 values).
  361.      * @var array $data For all file operations, this array must contain the
  362.      *    full path to the file or directory that is being operated on.  For
  363.      *    the rename command, the first parameter must be the file to rename,
  364.      *    the second its new name.
  365.      *
  366.      *    The installed_as operation contains 4 elements in this order:
  367.      *    1. Filename as listed in the filelist element from package.xml
  368.      *    2. Full path to the installed file
  369.      *    3. Full path from the php_dir configuration variable used in this
  370.      *       installation
  371.      *    4. Relative path from the php_dir that this file is installed in
  372.      */
  373.     function addFileOperation($type, $data)
  374.     {
  375.         if (!is_array($data)) {
  376.             return $this->raiseError('Internal Error: $data in addFileOperation'
  377.                 . ' must be an array, was ' . gettype($data));
  378.         }
  379.         if ($type == 'chmod') {
  380.             $octmode = decoct($data[0]);
  381.             $this->log(3, "adding to transaction: $type $octmode $data[1]");
  382.         } else {
  383.             $this->log(3, "adding to transaction: $type " . implode(" ", $data));
  384.         }
  385.         $this->file_operations[] = array($type, $data);
  386.     }
  387.  
  388.     // }}}
  389.     // {{{ startFileTransaction()
  390.  
  391.     function startFileTransaction($rollback_in_case = false)
  392.     {
  393.         if (count($this->file_operations) && $rollback_in_case) {
  394.             $this->rollbackFileTransaction();
  395.         }
  396.         $this->file_operations = array();
  397.     }
  398.  
  399.     // }}}
  400.     // {{{ commitFileTransaction()
  401.  
  402.     function commitFileTransaction()
  403.     {
  404.         $n = count($this->file_operations);
  405.         $this->log(2, "about to commit $n file operations");
  406.         // {{{ first, check permissions and such manually
  407.         $errors = array();
  408.         foreach ($this->file_operations as $tr) {
  409.             list($type, $data) = $tr;
  410.             switch ($type) {
  411.                 case 'rename':
  412.                     if (!file_exists($data[0])) {
  413.                         $errors[] = "cannot rename file $data[0], doesn't exist";
  414.                     }
  415.                     // check that dest dir. is writable
  416.                     if (!is_writable(dirname($data[1]))) {
  417.                         $errors[] = "permission denied ($type): $data[1]";
  418.                     }
  419.                     break;
  420.                 case 'chmod':
  421.                     // check that file is writable
  422.                     if (!is_writable($data[1])) {
  423.                         $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
  424.                     }
  425.                     break;
  426.                 case 'delete':
  427.                     if (!file_exists($data[0])) {
  428.                         $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
  429.                     }
  430.                     // check that directory is writable
  431.                     if (file_exists($data[0]) && !is_writable(dirname($data[0]))) {
  432.                         $errors[] = "permission denied ($type): $data[0]";
  433.                     }
  434.                     break;
  435.             }
  436.  
  437.         }
  438.         // }}}
  439.         $m = sizeof($errors);
  440.         if ($m > 0) {
  441.             foreach ($errors as $error) {
  442.                 $this->log(1, $error);
  443.             }
  444.             return false;
  445.         }
  446.         // {{{ really commit the transaction
  447.         foreach ($this->file_operations as $tr) {
  448.             list($type, $data) = $tr;
  449.             switch ($type) {
  450.                 case 'rename':
  451.                     @unlink($data[1]);
  452.                     @rename($data[0], $data[1]);
  453.                     $this->log(3, "+ mv $data[0] $data[1]");
  454.                     break;
  455.                 case 'chmod':
  456.                     @chmod($data[1], $data[0]);
  457.                     $octmode = decoct($data[0]);
  458.                     $this->log(3, "+ chmod $octmode $data[1]");
  459.                     break;
  460.                 case 'delete':
  461.                     @unlink($data[0]);
  462.                     $this->log(3, "+ rm $data[0]");
  463.                     break;
  464.                 case 'rmdir':
  465.                     @rmdir($data[0]);
  466.                     $this->log(3, "+ rmdir $data[0]");
  467.                     break;
  468.                 case 'installed_as':
  469.                     $this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1];
  470.                     if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  471.                         $this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true;
  472.                         while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  473.                               && $data[3] != '.') {
  474.                             $this->pkginfo['filelist']['dirtree']
  475.                                 [$this->_prependPath($data[3], $data[2])] = true;
  476.                             $data[3] = dirname($data[3]);
  477.                         }
  478.                     }
  479.                     break;
  480.             }
  481.         }
  482.         // }}}
  483.         $this->log(2, "successfully committed $n file operations");
  484.         $this->file_operations = array();
  485.         return true;
  486.     }
  487.  
  488.     // }}}
  489.     // {{{ rollbackFileTransaction()
  490.  
  491.     function rollbackFileTransaction()
  492.     {
  493.         $n = count($this->file_operations);
  494.         $this->log(2, "rolling back $n file operations");
  495.         foreach ($this->file_operations as $tr) {
  496.             list($type, $data) = $tr;
  497.             switch ($type) {
  498.                 case 'rename':
  499.                     @unlink($data[0]);
  500.                     $this->log(3, "+ rm $data[0]");
  501.                     break;
  502.                 case 'mkdir':
  503.                     @rmdir($data[0]);
  504.                     $this->log(3, "+ rmdir $data[0]");
  505.                     break;
  506.                 case 'chmod':
  507.                     break;
  508.                 case 'delete':
  509.                     break;
  510.                 case 'installed_as':
  511.                     if (isset($this->pkginfo['filelist'])) {
  512.                         unset($this->pkginfo['filelist'][$data[0]]['installed_as']);
  513.                     }
  514.                     if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
  515.                         unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]);
  516.                         while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
  517.                               && $data[3] != '.') {
  518.                             unset($this->pkginfo['filelist']['dirtree']
  519.                                 [$this->_prependPath($data[3], $data[2])]);
  520.                             $data[3] = dirname($data[3]);
  521.                         }
  522.                     }
  523.                     if (isset($this->pkginfo['filelist']['dirtree'])
  524.                           && !count($this->pkginfo['filelist']['dirtree'])) {
  525.                         unset($this->pkginfo['filelist']['dirtree']);
  526.                     }
  527.                     break;
  528.             }
  529.         }
  530.         $this->file_operations = array();
  531.     }
  532.  
  533.     // }}}
  534.     // {{{ mkDirHier($dir)
  535.  
  536.     function mkDirHier($dir)
  537.     {
  538.         $this->addFileOperation('mkdir', array($dir));
  539.         return parent::mkDirHier($dir);
  540.     }
  541.  
  542.     // }}}
  543.     // {{{ _prependPath($path, $prepend)
  544.  
  545.     function _prependPath($path, $prepend)
  546.     {
  547.         if (strlen($prepend) > 0) {
  548.             if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
  549.                 $path = $prepend . substr($path, 2);
  550.             } else {
  551.                 $path = $prepend . $path;
  552.             }
  553.         }
  554.         return $path;
  555.     }
  556.  
  557.     // }}}
  558.     // {{{ download()
  559.  
  560.     /**
  561.      * Download any files and their dependencies, if necessary
  562.      *
  563.      * @param array a mixed list of package names, local files, or package.xml
  564.      * @param PEAR_Config
  565.      * @param array options from the command line
  566.      * @param array this is the array that will be populated with packages to
  567.      *              install.  Format of each entry:
  568.      *
  569.      * <code>
  570.      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
  571.      *    'info' => array() // parsed package.xml
  572.      * );
  573.      * </code>
  574.      * @param array this will be populated with any error messages
  575.      * @param false private recursion variable
  576.      * @param false private recursion variable
  577.      * @param false private recursion variable
  578.      * @deprecated in favor of PEAR_Downloader
  579.      */
  580.     function download($packages, $options, &$config, &$installpackages,
  581.                       &$errors, $installed = false, $willinstall = false, $state = false)
  582.     {
  583.         // trickiness: initialize here
  584.         parent::PEAR_Downloader($this->ui, $options, $config);
  585.         $ret = parent::download($packages);
  586.         $errors = $this->getErrorMsgs();
  587.         $installpackages = $this->getDownloadedPackages();
  588.         trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
  589.                       "in favor of PEAR_Downloader class", E_USER_WARNING);
  590.         return $ret;
  591.     }
  592.  
  593.     // }}}
  594.     // {{{ install()
  595.  
  596.     /**
  597.      * Installs the files within the package file specified.
  598.      *
  599.      * @param string $pkgfile path to the package file
  600.      * @param array $options
  601.      * recognized options:
  602.      * - installroot   : optional prefix directory for installation
  603.      * - force         : force installation
  604.      * - register-only : update registry but don't install files
  605.      * - upgrade       : upgrade existing install
  606.      * - soft          : fail silently
  607.      * - nodeps        : ignore dependency conflicts/missing dependencies
  608.      * - alldeps       : install all dependencies
  609.      * - onlyreqdeps   : install only required dependencies
  610.      *
  611.      * @return array|PEAR_Error package info if successful
  612.      */
  613.  
  614.     function install($pkgfile, $options = array())
  615.     {
  616.         $php_dir = $this->config->get('php_dir');
  617.         if (isset($options['installroot'])) {
  618.             if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  619.                 $options['installroot'] = substr($options['installroot'], 0, -1);
  620.             }
  621.             $php_dir = $this->_prependPath($php_dir, $options['installroot']);
  622.             $this->installroot = $options['installroot'];
  623.         } else {
  624.             $this->installroot = '';
  625.         }
  626.         $this->registry = &new PEAR_Registry($php_dir);
  627.         //  ==> XXX should be removed later on
  628.         $flag_old_format = false;
  629.  
  630.         if (substr($pkgfile, -4) == '.xml') {
  631.             $descfile = $pkgfile;
  632.         } else {
  633.             // {{{ Decompress pack in tmp dir -------------------------------------
  634.  
  635.             // To allow relative package file names
  636.             $pkgfile = realpath($pkgfile);
  637.  
  638.             if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
  639.                 return $tmpdir;
  640.             }
  641.             $this->log(3, '+ tmp dir created at ' . $tmpdir);
  642.  
  643.             $tar = new Archive_Tar($pkgfile);
  644.             if (!@$tar->extract($tmpdir)) {
  645.                 return $this->raiseError("unable to unpack $pkgfile");
  646.             }
  647.  
  648.             // {{{ Look for existing package file
  649.             $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml';
  650.  
  651.             if (!is_file($descfile)) {
  652.                 // ----- Look for old package archive format
  653.                 // In this format the package.xml file was inside the
  654.                 // Package-n.n directory
  655.                 $dp = opendir($tmpdir);
  656.                 do {
  657.                     $pkgdir = readdir($dp);
  658.                 } while ($pkgdir{0} == '.');
  659.  
  660.                 $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml';
  661.                 $flag_old_format = true;
  662.                 $this->log(0, "warning : you are using an archive with an old format");
  663.             }
  664.             // }}}
  665.             // <== XXX This part should be removed later on
  666.             // }}}
  667.         }
  668.  
  669.         if (!is_file($descfile)) {
  670.             return $this->raiseError("no package.xml file after extracting the archive");
  671.         }
  672.  
  673.         // Parse xml file -----------------------------------------------
  674.         $pkginfo = $this->infoFromDescriptionFile($descfile);
  675.         if (PEAR::isError($pkginfo)) {
  676.             return $pkginfo;
  677.         }
  678.         $this->validatePackageInfo($pkginfo, $errors, $warnings);
  679.         // XXX We allow warnings, do we have to do it?
  680.         if (count($errors)) {
  681.             if (empty($options['force'])) {
  682.                 return $this->raiseError("The following errors where found (use force option to install anyway):\n".
  683.                                          implode("\n", $errors));
  684.             } else {
  685.                 $this->log(0, "warning : the following errors were found:\n".
  686.                            implode("\n", $errors));
  687.             }
  688.         }
  689.  
  690.         $pkgname = $pkginfo['package'];
  691.  
  692.         // {{{ Check dependencies -------------------------------------------
  693.         if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) {
  694.             $dep_errors = '';
  695.             $error = $this->checkDeps($pkginfo, $dep_errors);
  696.             if ($error == true) {
  697.                 if (empty($options['soft'])) {
  698.                     $this->log(0, substr($dep_errors, 1));
  699.                 }
  700.                 return $this->raiseError("$pkgname: Dependencies failed");
  701.             } else if (!empty($dep_errors)) {
  702.                 // Print optional dependencies
  703.                 if (empty($options['soft'])) {
  704.                     $this->log(0, $dep_errors);
  705.                 }
  706.             }
  707.         }
  708.         // }}}
  709.  
  710.         // {{{ checks to do when not in "force" mode
  711.         if (empty($options['force'])) {
  712.             $test = $this->registry->checkFileMap($pkginfo);
  713.             if (sizeof($test)) {
  714.                 $tmp = $test;
  715.                 foreach ($tmp as $file => $pkg) {
  716.                     if ($pkg == $pkgname) {
  717.                         unset($test[$file]);
  718.                     }
  719.                 }
  720.                 if (sizeof($test)) {
  721.                     $msg = "$pkgname: conflicting files found:\n";
  722.                     $longest = max(array_map("strlen", array_keys($test)));
  723.                     $fmt = "%${longest}s (%s)\n";
  724.                     foreach ($test as $file => $pkg) {
  725.                         $msg .= sprintf($fmt, $file, $pkg);
  726.                     }
  727.                     return $this->raiseError($msg);
  728.                 }
  729.             }
  730.         }
  731.         // }}}
  732.  
  733.         $this->startFileTransaction();
  734.  
  735.         if (empty($options['upgrade'])) {
  736.             // checks to do only when installing new packages
  737.             if (empty($options['force']) && $this->registry->packageExists($pkgname)) {
  738.                 return $this->raiseError("$pkgname already installed");
  739.             }
  740.         } else {
  741.             if ($this->registry->packageExists($pkgname)) {
  742.                 $v1 = $this->registry->packageInfo($pkgname, 'version');
  743.                 $v2 = $pkginfo['version'];
  744.                 $cmp = version_compare("$v1", "$v2", 'gt');
  745.                 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
  746.                     return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
  747.                 }
  748.                 if (empty($options['register-only'])) {
  749.                     // when upgrading, remove old release's files first:
  750.                     if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) {
  751.                         return $this->raiseError($err);
  752.                     }
  753.                 }
  754.             }
  755.         }
  756.  
  757.         // {{{ Copy files to dest dir ---------------------------------------
  758.  
  759.         // info from the package it self we want to access from _installFile
  760.         $this->pkginfo = &$pkginfo;
  761.         // used to determine whether we should build any C code
  762.         $this->source_files = 0;
  763.  
  764.         if (empty($options['register-only'])) {
  765.             if (!is_dir($php_dir)) {
  766.                 return $this->raiseError("no script destination directory\n",
  767.                                          null, PEAR_ERROR_DIE);
  768.             }
  769.  
  770.             $tmp_path = dirname($descfile);
  771.             if (substr($pkgfile, -4) != '.xml') {
  772.                 $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version'];
  773.             }
  774.  
  775.             //  ==> XXX This part should be removed later on
  776.             if ($flag_old_format) {
  777.                 $tmp_path = dirname($descfile);
  778.             }
  779.             // <== XXX This part should be removed later on
  780.  
  781.             // {{{ install files
  782.             foreach ($pkginfo['filelist'] as $file => $atts) {
  783.                 $this->expectError(PEAR_INSTALLER_FAILED);
  784.                 $res = $this->_installFile($file, $atts, $tmp_path, $options);
  785.                 $this->popExpect();
  786.                 if (PEAR::isError($res)) {
  787.                     if (empty($options['ignore-errors'])) {
  788.                         $this->rollbackFileTransaction();
  789.                         if ($res->getMessage() == "file does not exist") {
  790.                             $this->raiseError("file $file in package.xml does not exist");
  791.                         }
  792.                         return $this->raiseError($res);
  793.                     } else {
  794.                         $this->log(0, "Warning: " . $res->getMessage());
  795.                     }
  796.                 }
  797.                 if ($res != PEAR_INSTALLER_OK) {
  798.                     // Do not register files that were not installed
  799.                     unset($pkginfo['filelist'][$file]);
  800.                 }
  801.             }
  802.             // }}}
  803.  
  804.             // {{{ compile and install source files
  805.             if ($this->source_files > 0 && empty($options['nobuild'])) {
  806.                 $this->log(1, "$this->source_files source files, building");
  807.                 $bob = &new PEAR_Builder($this->ui);
  808.                 $bob->debug = $this->debug;
  809.                 $built = $bob->build($descfile, array(&$this, '_buildCallback'));
  810.                 if (PEAR::isError($built)) {
  811.                     $this->rollbackFileTransaction();
  812.                     return $built;
  813.                 }
  814.                 $this->log(1, "\nBuild process completed successfully");
  815.                 foreach ($built as $ext) {
  816.                     $bn = basename($ext['file']);
  817.                     list($_ext_name, ) = explode('.', $bn);
  818.                     if (extension_loaded($_ext_name)) {
  819.                         $this->raiseError("Extension '$_ext_name' already loaded. Please unload it ".
  820.                                           "in your php.ini file prior to install or upgrade it.");
  821.                     }
  822.                     $dest = $this->config->get('ext_dir') . DIRECTORY_SEPARATOR . $bn;
  823.                     $this->log(1, "Installing '$bn' at ext_dir ($dest)");
  824.                     $this->log(3, "+ cp $ext[file] ext_dir ($dest)");
  825.                     $copyto = $this->_prependPath($dest, $this->installroot);
  826.                     if (!@copy($ext['file'], $copyto)) {
  827.                         $this->rollbackFileTransaction();
  828.                         return $this->raiseError("failed to copy $bn to $copyto");
  829.                     }
  830.                     $pkginfo['filelist'][$bn] = array(
  831.                         'role' => 'ext',
  832.                         'installed_as' => $dest,
  833.                         'php_api' => $ext['php_api'],
  834.                         'zend_mod_api' => $ext['zend_mod_api'],
  835.                         'zend_ext_api' => $ext['zend_ext_api'],
  836.                         );
  837.                 }
  838.             }
  839.             // }}}
  840.         }
  841.  
  842.         if (!$this->commitFileTransaction()) {
  843.             $this->rollbackFileTransaction();
  844.             return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
  845.         }
  846.         // }}}
  847.  
  848.         $ret = false;
  849.         // {{{ Register that the package is installed -----------------------
  850.         if (empty($options['upgrade'])) {
  851.             // if 'force' is used, replace the info in registry
  852.             if (!empty($options['force']) && $this->registry->packageExists($pkgname)) {
  853.                 $this->registry->deletePackage($pkgname);
  854.             }
  855.             $ret = $this->registry->addPackage($pkgname, $pkginfo);
  856.         } else {
  857.             // new: upgrade installs a package if it isn't installed
  858.             if (!$this->registry->packageExists($pkgname)) {
  859.                 $ret = $this->registry->addPackage($pkgname, $pkginfo);
  860.             } else {
  861.                 $ret = $this->registry->updatePackage($pkgname, $pkginfo, false);
  862.             }
  863.         }
  864.         if (!$ret) {
  865.             return $this->raiseError("Adding package $pkgname to registry failed");
  866.         }
  867.         // }}}
  868.         return $pkginfo;
  869.     }
  870.  
  871.     // }}}
  872.     // {{{ uninstall()
  873.  
  874.     /**
  875.      * Uninstall a package
  876.      *
  877.      * This method removes all files installed by the application, and then
  878.      * removes any empty directories.
  879.      * @param string package name
  880.      * @param array Command-line options.  Possibilities include:
  881.      *
  882.      *              - installroot: base installation dir, if not the default
  883.      *              - nodeps: do not process dependencies of other packages to ensure
  884.      *                        uninstallation does not break things
  885.      */
  886.     function uninstall($package, $options = array())
  887.     {
  888.         $php_dir = $this->config->get('php_dir');
  889.         if (isset($options['installroot'])) {
  890.             if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
  891.                 $options['installroot'] = substr($options['installroot'], 0, -1);
  892.             }
  893.             $this->installroot = $options['installroot'];
  894.             $php_dir = $this->_prependPath($php_dir, $this->installroot);
  895.         } else {
  896.             $this->installroot = '';
  897.         }
  898.         $this->registry = &new PEAR_Registry($php_dir);
  899.         $filelist = $this->registry->packageInfo($package, 'filelist');
  900.         if ($filelist == null) {
  901.             return $this->raiseError("$package not installed");
  902.         }
  903.         if (empty($options['nodeps'])) {
  904.             $depchecker = &new PEAR_Dependency($this->registry);
  905.             $error = $depchecker->checkPackageUninstall($errors, $warning, $package);
  906.             if ($error) {
  907.                 return $this->raiseError($errors . 'uninstall failed');
  908.             }
  909.             if ($warning) {
  910.                 $this->log(0, $warning);
  911.             }
  912.         }
  913.         // {{{ Delete the files
  914.         $this->startFileTransaction();
  915.         if (PEAR::isError($err = $this->_deletePackageFiles($package))) {
  916.             $this->rollbackFileTransaction();
  917.             return $this->raiseError($err);
  918.         }
  919.         if (!$this->commitFileTransaction()) {
  920.             $this->rollbackFileTransaction();
  921.             return $this->raiseError("uninstall failed");
  922.         } else {
  923.             $this->startFileTransaction();
  924.             if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) {
  925.                 return $this->registry->deletePackage($package);
  926.             }
  927.             // attempt to delete empty directories
  928.             uksort($filelist['dirtree'], array($this, '_sortDirs'));
  929.             foreach($filelist['dirtree'] as $dir => $notused) {
  930.                 $this->addFileOperation('rmdir', array($dir));
  931.             }
  932.             if (!$this->commitFileTransaction()) {
  933.                 $this->rollbackFileTransaction();
  934.             }
  935.         }
  936.         // }}}
  937.  
  938.         // Register that the package is no longer installed
  939.         return $this->registry->deletePackage($package);
  940.     }
  941.  
  942.     // }}}
  943.     // {{{ _sortDirs()
  944.     function _sortDirs($a, $b)
  945.     {
  946.         if (strnatcmp($a, $b) == -1) return 1;
  947.         if (strnatcmp($a, $b) == 1) return -1;
  948.         return 0;
  949.     }
  950.  
  951.     // }}}
  952.     // {{{ checkDeps()
  953.  
  954.     /**
  955.      * Check if the package meets all dependencies
  956.      *
  957.      * @param  array   Package information (passed by reference)
  958.      * @param  string  Error message (passed by reference)
  959.      * @return boolean False when no error occured, otherwise true
  960.      */
  961.     function checkDeps(&$pkginfo, &$errors)
  962.     {
  963.         if (empty($this->registry)) {
  964.             $this->registry = &new PEAR_Registry($this->config->get('php_dir'));
  965.         }
  966.         $depchecker = &new PEAR_Dependency($this->registry);
  967.         $error = $errors = '';
  968.         $failed_deps = $optional_deps = array();
  969.         if (is_array($pkginfo['release_deps'])) {
  970.             foreach($pkginfo['release_deps'] as $dep) {
  971.                 $code = $depchecker->callCheckMethod($error, $dep);
  972.                 if ($code) {
  973.                     if (isset($dep['optional']) && $dep['optional'] == 'yes') {
  974.                         $optional_deps[] = array($dep, $code, $error);
  975.                     } else {
  976.                         $failed_deps[] = array($dep, $code, $error);
  977.                     }
  978.                 }
  979.             }
  980.             // {{{ failed dependencies
  981.             $n = count($failed_deps);
  982.             if ($n > 0) {
  983.                 for ($i = 0; $i < $n; $i++) {
  984.                     if (isset($failed_deps[$i]['type'])) {
  985.                         $type = $failed_deps[$i]['type'];
  986.                     } else {
  987.                         $type = 'pkg';
  988.                     }
  989.                     switch ($failed_deps[$i][1]) {
  990.                         case PEAR_DEPENDENCY_MISSING:
  991.                             if ($type == 'pkg') {
  992.                                 // install
  993.                             }
  994.                             $errors .= "\n" . $failed_deps[$i][2];
  995.                             break;
  996.                         case PEAR_DEPENDENCY_UPGRADE_MINOR:
  997.                             if ($type == 'pkg') {
  998.                                 // upgrade
  999.                             }
  1000.                             $errors .= "\n" . $failed_deps[$i][2];
  1001.                             break;
  1002.                         default:
  1003.                             $errors .= "\n" . $failed_deps[$i][2];
  1004.                             break;
  1005.                     }
  1006.                 }
  1007.                 return true;
  1008.             }
  1009.             // }}}
  1010.  
  1011.             // {{{ optional dependencies
  1012.             $count_optional = count($optional_deps);
  1013.             if ($count_optional > 0) {
  1014.                 $errors = "Optional dependencies:";
  1015.  
  1016.                 for ($i = 0; $i < $count_optional; $i++) {
  1017.                     if (isset($optional_deps[$i]['type'])) {
  1018.                         $type = $optional_deps[$i]['type'];
  1019.                     } else {
  1020.                         $type = 'pkg';
  1021.                     }
  1022.                     switch ($optional_deps[$i][1]) {
  1023.                         case PEAR_DEPENDENCY_MISSING:
  1024.                         case PEAR_DEPENDENCY_UPGRADE_MINOR:
  1025.                         default:
  1026.                             $errors .= "\n" . $optional_deps[$i][2];
  1027.                             break;
  1028.                     }
  1029.                 }
  1030.                 return false;
  1031.             }
  1032.             // }}}
  1033.         }
  1034.         return false;
  1035.     }
  1036.  
  1037.     // }}}
  1038.     // {{{ _buildCallback()
  1039.  
  1040.     function _buildCallback($what, $data)
  1041.     {
  1042.         if (($what == 'cmdoutput' && $this->debug > 1) ||
  1043.             ($what == 'output' && $this->debug > 0)) {
  1044.             $this->ui->outputData(rtrim($data), 'build');
  1045.         }
  1046.     }
  1047.  
  1048.     // }}}
  1049. }
  1050.  
  1051. // {{{ md5_file() utility function
  1052. if (!function_exists("md5_file")) {
  1053.     function md5_file($filename) {
  1054.         $fp = fopen($filename, "r");
  1055.         if (!$fp) return null;
  1056.         $contents = fread($fp, filesize($filename));
  1057.         fclose($fp);
  1058.         return md5($contents);
  1059.     }
  1060. }
  1061. // }}}
  1062.  
  1063. ?>
  1064.