| // | Tomas V.V.Cox | // +----------------------------------------------------------------------+ // // $Id$ require_once 'PEAR/Common.php'; require_once 'PEAR/Registry.php'; require_once 'PEAR/Dependency.php'; define('PEAR_INSTALLER_OK', 1); define('PEAR_INSTALLER_FAILED', 0); define('PEAR_INSTALLER_SKIPPED', -1); /** * Administration class used to install PEAR packages and maintain the * installed package database. * * TODO: * - Check dependencies break on package uninstall (when no force given) * * @since PHP 4.0.2 * @author Stig Bakken */ class PEAR_Installer extends PEAR_Common { // {{{ properties /** name of the package directory, for example Foo-1.0 * @var string */ var $pkgdir; /** directory where PHP code files go * @var string */ var $phpdir; /** directory where PHP extension files go * @var string */ var $extdir; /** directory where documentation goes * @var string */ var $docdir; /** directory where the package wants to put files, relative * to one of the previous dirs * @var string */ var $destdir = ''; /** debug level * @var int */ var $debug = 1; /** temporary directory * @var string */ var $tmpdir; /** PEAR_Registry object used by the installer * @var object */ var $registry; // }}} // {{{ constructor /** * PEAR_Installer constructor. * * @param object $ui user interface object (instance of PEAR_Frontend_*) * * @access public */ function PEAR_Installer(&$ui) { $this->PEAR_Common(); $this->setFrontendObject($ui); $this->debug = $this->config->get('verbose'); $this->registry = &new PEAR_Registry($this->config->get('php_dir')); } // }}} // {{{ _deletePackageFiles() /** * Delete a package's installed files, remove empty directories. * * @param string $package package name * * @return bool TRUE on success, or a PEAR error on failure * * @access private */ function _deletePackageFiles($package) { if (!strlen($package)) { return $this->raiseError("No package to uninstall given"); } $filelist = $this->registry->packageInfo($package, 'filelist'); if ($filelist == null) { return $this->raiseError("$package not installed"); } foreach ($filelist as $file => $props) { if (empty($props['installed_as'])) { continue; } $path = $props['installed_as']; if (!@unlink($path)) { $this->log(2, "unable to delete: $path"); } else { $this->log(2, "deleted file $path"); // Delete package directory if it's empty if (@rmdir(dirname($path))) { $this->log(3, "+ rmdir $path"); } } } return true; } // }}} // {{{ _installFile() function _installFile($file, $atts, $tmp_path) { static $os; if (isset($atts['platform'])) { if (empty($os)) { include_once "OS/Guess.php"; $os = new OS_Guess(); } // return if this file is meant for another platform if (!$os->matchSignature($atts['platform'])) { $this->log(2, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")"); return PEAR_INSTALLER_SKIPPED; } } switch ($atts['role']) { case 'test': case 'data': case 'ext': // don't install test files for now $this->log(2, "$file: $atts[role] file not installed yet"); return PEAR_INSTALLER_OK; case 'doc': $dest_dir = $this->config->get('doc_dir') . DIRECTORY_SEPARATOR . $this->pkginfo['package']; break; case 'extsrc': // don't install test files for now $this->log(2, "$file: no support for building extensions yet"); return PEAR_INSTALLER_OK; case 'php': $dest_dir = $this->config->get('php_dir'); break; case 'script': { $dest_dir = $this->config->get('bin_dir'); break; } default: break; } if (!isset($atts['baseinstalldir']) || $atts['baseinstalldir'] == '/' || $atts['baseinstalldir'] == DIRECTORY_SEPARATOR) { $atts['baseinstalldir'] = ''; } if (!empty($atts['baseinstalldir']) && $atts['role'] != 'doc') { $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir']; } if (dirname($file) != '.' && empty($atts['install-as'])) { $dest_dir .= DIRECTORY_SEPARATOR . dirname($file); } if (empty($atts['install-as'])) { $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file); } else { $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as']; } $dest_file = preg_replace('!//+!', '/', $dest_file); if (!@is_dir($dest_dir)) { if (!$this->mkDirHier($dest_dir)) { return $this->raiseError(PEAR_INSTALLER_FAILED, "failed to mkdir $dest_dir"); } $this->log(3, "+ mkdir $dest_dir"); } $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file; if (empty($atts['replacements'])) { if (!@copy($orig_file, $dest_file)) { return $this->raiseError(PEAR_INSTALLER_FAILED, "failed to copy $orig_file to $dest_file"); } $this->log(3, "+ cp $orig_file $dest_file"); } else { $fp = fopen($orig_file, "r"); $contents = fread($fp, filesize($orig_file)); fclose($fp); $subst_from = $subst_to = array(); foreach ($atts['replacements'] as $a) { $to = ''; if ($a['type'] == 'php-const') { if (preg_match('/^[a-z0-9_]+$/i', $a['to'])) { eval("\$to = $a[to];"); } else { $this->log(0, "invalid php-const replacement: $a[to]"); continue; } } elseif ($a['type'] == 'pear-config') { $to = $this->config->get($a['to']); } if ($to) { $subst_from = $a['from']; $subst_to = $to; } } $this->log(2, "doing ".sizeof($subst_from)." substitution(s) for $dest_file"); if (sizeof($subst_from)) { $contents = str_replace($subst_from, $subst_to, $contents); } $wp = @fopen($dest_file, "w"); if (!is_resource($wp)) { return $this->raiseError(PEAR_INSTALLER_FAILED, "failed to create $dest_file"); } fwrite($wp, $contents); fclose($wp); } if (!OS_WINDOWS) { if ($atts['role'] == 'script') { $mode = 0777 & ~$this->config->get('umask'); $this->log(3, "+ chmod +x $dest_file"); } else { $mode = 0666 & ~$this->config->get('umask'); } if (!@chmod($dest_file, $mode)) { $this->log(0, "failed to change mode of $dest_file"); } } // Store the full path where the file was installed for easy unistall $this->pkginfo['filelist'][$file]['installed_as'] = $dest_file; $this->log(2, "installed file $dest_file"); return PEAR_INSTALLER_OK; } // }}} // {{{ getPackageDownloadUrl() function getPackageDownloadUrl($package) { if ($this === null || $this->config === null) { $package = "http://pear.php.net/get/$package"; } else { $package = "http://" . $this->config->get('master_server') . "/get/$package"; } if (!extension_loaded("zlib")) { $package .= '?uncompress=yes'; } return $package; } // }}} // {{{ install() /** * Installs the files within the package file specified. * * @param $pkgfile path to the package file * * @return array package info if successful, null if not */ function install($pkgfile, $options = array()) { // recognized options: // - register-only : update registry but don't install files // - upgrade : upgrade existing install // - soft : fail silently // $need_download = false; // ==> XXX should be removed later on $flag_old_format = false; if (preg_match('#^(http|ftp)://#', $pkgfile)) { $need_download = true; } elseif (!@is_file($pkgfile)) { if ($this->validPackageName($pkgfile)) { if ($this->registry->packageExists($pkgfile) && empty($options['upgrade'])) { return $this->raiseError("$pkgfile already installed"); } $pkgfile = $this->getPackageDownloadUrl($pkgfile); $need_download = true; } else { if (strlen($pkgfile)) { return $this->raiseError("Could not open the package file: $pkgfile"); } else { return $this->raiseError("No package file given"); } } } // Download package ----------------------------------------------- if ($need_download) { $downloaddir = $this->config->get('download_dir'); if (empty($downloaddir)) { if (PEAR::isError($downloaddir = $this->mkTempDir())) { return $downloaddir; } $this->log(2, '+ tmp dir created at ' . $downloaddir); } $callback = $this->ui ? array(&$this, '_downloadCallback') : null; $file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback); if (PEAR::isError($file)) { return $this->raiseError($file); } $pkgfile = $file; } if (substr($pkgfile, -4) == '.xml') { $descfile = $pkgfile; } else { // Decompress pack in tmp dir ------------------------------------- // To allow relative package file names $oldcwd = getcwd(); if (@chdir(dirname($pkgfile))) { $pkgfile = getcwd() . DIRECTORY_SEPARATOR . basename($pkgfile); chdir($oldcwd); } if (PEAR::isError($tmpdir = $this->mkTempDir())) { return $tmpdir; } $this->log(2, '+ tmp dir created at ' . $tmpdir); $tar = new Archive_Tar($pkgfile); if (!@$tar->extract($tmpdir)) { return $this->raiseError("unable to unpack $pkgfile"); } // ----- Look for existing package file $descfile = $tmpdir . DIRECTORY_SEPARATOR . 'package.xml'; if (!is_file($descfile)) { // ----- Look for old package archive format // In this format the package.xml file was inside the // Package-n.n directory $dp = opendir($tmpdir); do { $pkgdir = readdir($dp); } while ($pkgdir{0} == '.'); $descfile = $tmpdir . DIRECTORY_SEPARATOR . $pkgdir . DIRECTORY_SEPARATOR . 'package.xml'; $flag_old_format = true; $this->log(0, "warning : you are using an archive with an old format"); } // <== XXX This part should be removed later on } if (!is_file($descfile)) { return $this->raiseError("no package.xml file after extracting the archive"); } // Parse xml file ----------------------------------------------- $pkginfo = $this->infoFromDescriptionFile($descfile); if (PEAR::isError($pkginfo)) { return $pkginfo; } $pkgname = $pkginfo['package']; // Check dependencies ------------------------------------------- if (isset($pkginfo['release_deps']) && !isset($options['nodeps'])) { $error = $this->checkDeps($pkginfo); if ($error) { if (empty($options['soft'])) { $this->log(0, $error); } return $this->raiseError("$pkgname: dependencies failed"); } } if (empty($options['upgrade'])) { // checks to do only when installing new packages if (empty($options['force']) && $this->registry->packageExists($pkgname)) { return $this->raiseError("$pkgname already installed"); } } else { // check to do only when upgrading packages if (!$this->registry->packageExists($pkgname)) { return $this->raiseError("$pkgname not installed"); } $v1 = $this->registry->packageInfo($pkgname, 'version'); $v2 = $pkginfo['version']; $cmp = version_compare($v1, $v2, 'gt'); if (empty($options['force']) && !version_compare($v2, $v1, 'gt')) { return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)"); } if (empty($options['register-only'])) { // when upgrading, remove old release's files first: if (PEAR::isError($err = $this->_deletePackageFiles($pkgname))) { return $this->raiseError($err); } } } // Copy files to dest dir --------------------------------------- // info from the package it self we want to access from _installFile $this->pkginfo = &$pkginfo; if (empty($options['register-only'])) { if (!is_dir($this->config->get('php_dir'))) { return $this->raiseError("no script destination directory\n", null, PEAR_ERROR_DIE); } // don't want strange characters $pkgname = ereg_replace ('[^a-zA-Z0-9._]', '_', $pkginfo['package']); $pkgversion = ereg_replace ('[^a-zA-Z0-9._\-]', '_', $pkginfo['version']); $tmp_path = dirname($descfile); if (substr($pkgfile, -4) != '.xml') { $tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkgversion; } // ==> XXX This part should be removed later on if ($flag_old_format) { $tmp_path = dirname($descfile); } // <== XXX This part should be removed later on foreach ($pkginfo['filelist'] as $file => $atts) { $this->expectError(PEAR_INSTALLER_FAILED); $res = $this->_installFile($file, $atts, $tmp_path); $this->popExpect(); if (PEAR::isError($res) && empty($options['force'])) { return $this->raiseError($res); } if ($res != PEAR_INSTALLER_OK) { // Do not register files that were not installed unset($pkginfo['filelist'][$file]); } } } // Register that the package is installed ----------------------- if (empty($options['upgrade'])) { // if 'force' is used, replace the info in registry if (!empty($options['force']) && $this->registry->packageExists($pkgname)) { $this->registry->deletePackage($pkgname); } $ret = $this->registry->addPackage($pkgname, $pkginfo); } else { $ret = $this->registry->updatePackage($pkgname, $pkginfo, false); } if (!$ret) { return null; } return $pkginfo; } // }}} // {{{ uninstall() function uninstall($package) { if (empty($this->registry)) { $this->registry = new PEAR_Registry($this->config->get('php_dir')); } // Delete the files if (PEAR::isError($err = $this->_deletePackageFiles($package))) { return $this->raiseError($err); } // Register that the package is no longer installed return $this->registry->deletePackage($package); } // }}} // {{{ checkDeps() function checkDeps(&$pkginfo) { $deps = &new PEAR_Dependency($this->registry); $errors = null; if (is_array($pkginfo['release_deps'])) { foreach($pkginfo['release_deps'] as $dep) { if ($error = $deps->callCheckMethod($dep)) { $errors .= "\n$error"; } } if ($errors) { return $errors; } } return false; } // }}} // {{{ _downloadCallback() function _downloadCallback($msg, $params = null) { switch ($msg) { case 'saveas': $this->log(1, "downloading $params ..."); break; case 'done': $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes'); break; } } // }}} } ?>