php-src/pear/PEAR/Installer.php

1442 lines
56 KiB
PHP
Raw Normal View History

<?php
//
// +----------------------------------------------------------------------+
2001-12-11 15:32:16 +00:00
// | PHP Version 4 |
// +----------------------------------------------------------------------+
2002-12-31 16:18:29 +00:00
// | Copyright (c) 1997-2003 The PHP Group |
// +----------------------------------------------------------------------+
// | This source file is subject to version 3.0 of the PHP license, |
// | that is bundled with this package in the file LICENSE, and is |
// | available through the world-wide-web at the following url: |
// | http://www.php.net/license/3_0.txt. |
// | If you did not receive a copy of the PHP license and are unable to |
// | obtain it through the world-wide-web, please send a note to |
// | license@php.net so we can mail you a copy immediately. |
// +----------------------------------------------------------------------+
2003-03-18 12:06:09 +00:00
// | Authors: Stig Bakken <ssb@php.net> |
// | Tomas V.V.Cox <cox@idecnet.com> |
// | Martin Jansen <mj@php.net> |
// +----------------------------------------------------------------------+
//
// $Id$
require_once 'PEAR/Common.php';
require_once 'PEAR/Registry.php';
require_once 'PEAR/Dependency.php';
require_once 'System.php';
define('PEAR_INSTALLER_OK', 1);
define('PEAR_INSTALLER_FAILED', 0);
define('PEAR_INSTALLER_SKIPPED', -1);
define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
/**
* Administration class used to install PEAR packages and maintain the
* installed package database.
*
* TODO:
* - Check dependencies break on package uninstall (when no force given)
* - add a guessInstallDest() method with the code from _installFile() and
* use that method in Registry::_rebuildFileMap() & Command_Registry::doList(),
* others..
*
* @since PHP 4.0.2
2003-03-18 12:06:09 +00:00
* @author Stig Bakken <ssb@php.net>
* @author Martin Jansen <mj@php.net>
*/
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;
/** installation root directory (ala PHP's INSTALL_ROOT or
* automake's DESTDIR
* @var string
*/
var $installroot = '';
/** debug level
* @var int
*/
var $debug = 1;
/** temporary directory
* @var string
*/
var $tmpdir;
/** PEAR_Registry object used by the installer
* @var object
*/
var $registry;
/** List of file transactions queued for an install/upgrade/uninstall.
*
* Format:
* array(
* 0 => array("rename => array("from-file", "to-file")),
* 1 => array("delete" => array("file-to-delete")),
* ...
* )
*
* @var array
*/
var $file_operations = array();
// }}}
// {{{ constructor
/**
* PEAR_Installer constructor.
*
* @param object $ui user interface object (instance of PEAR_Frontend_*)
*
* @access public
*/
function PEAR_Installer(&$ui)
{
parent::PEAR_Common();
$this->setFrontendObject($ui);
$this->debug = $this->config->get('verbose');
//$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
}
// }}}
2001-12-12 01:30:56 +00:00
// {{{ _deletePackageFiles()
/**
* Delete a package's installed files, does not remove empty directories.
*
* @param string $package package name
*
* @return bool TRUE on success, or a PEAR error on failure
*
* @access private
*/
2001-12-12 01:30:56 +00:00
function _deletePackageFiles($package)
{
if (!strlen($package)) {
return $this->raiseError("No package to uninstall given");
}
$filelist = $this->registry->packageInfo($package, 'filelist');
if ($filelist == null) {
2001-12-12 01:30:56 +00:00
return $this->raiseError("$package not installed");
}
foreach ($filelist as $file => $props) {
if (empty($props['installed_as'])) {
continue;
}
$path = $this->_prependPath($props['installed_as'], $this->installroot);
$this->addFileOperation('delete', array($path));
2001-12-12 01:30:56 +00:00
}
return true;
2001-12-12 01:30:56 +00:00
}
// }}}
// {{{ _installFile()
/**
* @param string filename
* @param array attributes from <file> tag in package.xml
* @param string path to install the file in
* @param array options from command-line
* @access private
*/
function _installFile($file, $atts, $tmp_path, $options)
{
2003-10-05 16:42:18 +00:00
// {{{ return if this file is meant for another platform
2002-05-19 06:19:26 +00:00
static $os;
if (isset($atts['platform'])) {
if (empty($os)) {
include_once "OS/Guess.php";
$os = new OS_Guess();
}
if (!$os->matchSignature($atts['platform'])) {
2002-10-12 00:40:16 +00:00
$this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
return PEAR_INSTALLER_SKIPPED;
2002-05-19 06:19:26 +00:00
}
}
2003-10-05 16:42:18 +00:00
// }}}
2002-05-19 06:19:26 +00:00
2003-10-05 16:42:18 +00:00
// {{{ assemble the destination paths
switch ($atts['role']) {
case 'doc':
case 'data':
case 'test':
$dest_dir = $this->config->get($atts['role'] . '_dir') .
DIRECTORY_SEPARATOR . $this->pkginfo['package'];
unset($atts['baseinstalldir']);
break;
case 'ext':
case 'php':
$dest_dir = $this->config->get($atts['role'] . '_dir');
break;
case 'script':
$dest_dir = $this->config->get('bin_dir');
break;
2002-06-02 13:07:19 +00:00
case 'src':
case 'extsrc':
2002-06-02 13:07:19 +00:00
$this->source_files++;
return;
default:
return $this->raiseError("Invalid role `$atts[role]' for file $file");
}
$save_destdir = $dest_dir;
if (!empty($atts['baseinstalldir'])) {
$dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
}
if (dirname($file) != '.' && empty($atts['install-as'])) {
$dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
}
if (empty($atts['install-as'])) {
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
} else {
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
}
$orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
// Clean up the DIRECTORY_SEPARATOR mess
$ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
DIRECTORY_SEPARATOR,
array($dest_file, $orig_file));
$installed_as = $dest_file;
$final_dest_file = $this->_prependPath($dest_file, $this->installroot);
$dest_dir = dirname($final_dest_file);
$dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
2003-10-05 16:42:18 +00:00
// }}}
if (!@is_dir($dest_dir)) {
if (!$this->mkDirHier($dest_dir)) {
return $this->raiseError("failed to mkdir $dest_dir",
PEAR_INSTALLER_FAILED);
}
$this->log(3, "+ mkdir $dest_dir");
}
if (empty($atts['replacements'])) {
if (!file_exists($orig_file)) {
return $this->raiseError("file does not exist",
PEAR_INSTALLER_FAILED);
}
if (!@copy($orig_file, $dest_file)) {
return $this->raiseError("failed to write $dest_file",
PEAR_INSTALLER_FAILED);
}
$this->log(3, "+ cp $orig_file $dest_file");
2002-10-12 00:40:16 +00:00
if (isset($atts['md5sum'])) {
$md5sum = md5_file($dest_file);
}
} else {
2003-10-05 16:42:18 +00:00
// {{{ file with replacements
if (!file_exists($orig_file)) {
return $this->raiseError("file does not exist",
PEAR_INSTALLER_FAILED);
}
$fp = fopen($orig_file, "r");
$contents = fread($fp, filesize($orig_file));
fclose($fp);
2002-10-12 00:40:16 +00:00
if (isset($atts['md5sum'])) {
$md5sum = md5($contents);
}
$subst_from = $subst_to = array();
foreach ($atts['replacements'] as $a) {
$to = '';
if ($a['type'] == 'php-const') {
if (preg_match('/^[a-z0-9_]+$/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 (is_null($to)) {
$this->log(0, "invalid pear-config replacement: $a[to]");
continue;
}
2002-06-02 13:07:19 +00:00
} elseif ($a['type'] == 'package-info') {
if (isset($this->pkginfo[$a['to']]) && is_string($this->pkginfo[$a['to']])) {
$to = $this->pkginfo[$a['to']];
} else {
$this->log(0, "invalid package-info replacement: $a[to]");
continue;
}
}
if (!is_null($to)) {
2002-06-02 13:07:19 +00:00
$subst_from[] = $a['from'];
$subst_to[] = $to;
}
}
$this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
if (sizeof($subst_from)) {
$contents = str_replace($subst_from, $subst_to, $contents);
}
2003-06-27 10:46:54 +00:00
$wp = @fopen($dest_file, "wb");
if (!is_resource($wp)) {
return $this->raiseError("failed to create $dest_file: $php_errormsg",
PEAR_INSTALLER_FAILED);
}
if (!fwrite($wp, $contents)) {
return $this->raiseError("failed writing to $dest_file: $php_errormsg",
PEAR_INSTALLER_FAILED);
}
fclose($wp);
2003-10-05 16:42:18 +00:00
// }}}
}
2003-10-05 16:42:18 +00:00
// {{{ check the md5
2002-10-12 00:40:16 +00:00
if (isset($md5sum)) {
if (strtolower($md5sum) == strtolower($atts['md5sum'])) {
2003-09-11 14:57:10 +00:00
$this->log(2, "md5sum ok: $final_dest_file");
2002-10-12 00:40:16 +00:00
} else {
if (empty($options['force'])) {
// delete the file
@unlink($dest_file);
return $this->raiseError("bad md5sum for file $final_dest_file",
PEAR_INSTALLER_FAILED);
} else {
$this->log(0, "warning : bad md5sum for file $final_dest_file");
}
2002-10-12 00:40:16 +00:00
}
}
2003-10-05 16:42:18 +00:00
// }}}
// {{{ set file permissions
if (!OS_WINDOWS) {
if ($atts['role'] == 'script') {
2002-06-02 13:07:19 +00:00
$mode = 0777 & ~(int)octdec($this->config->get('umask'));
$this->log(3, "+ chmod +x $dest_file");
} else {
2002-06-02 13:07:19 +00:00
$mode = 0666 & ~(int)octdec($this->config->get('umask'));
}
$this->addFileOperation("chmod", array($mode, $dest_file));
if (!@chmod($dest_file, $mode)) {
$this->log(0, "failed to change mode of $dest_file");
}
}
2003-10-05 16:42:18 +00:00
// }}}
$this->addFileOperation("rename", array($dest_file, $final_dest_file));
// Store the full path where the file was installed for easy unistall
$this->addFileOperation("installed_as", array($file, $installed_as,
$save_destdir, dirname(substr($dest_file, strlen($save_destdir)))));
//$this->log(2, "installed: $dest_file");
return PEAR_INSTALLER_OK;
}
// }}}
// {{{ addFileOperation()
/**
* Add a file operation to the current file transaction.
*
* @see startFileTransaction()
* @var string $type This can be one of:
* - rename: rename a file ($data has 2 values)
2003-09-17 03:13:56 +00:00
* - chmod: change permissions on a file ($data has 2 values)
* - delete: delete a file ($data has 1 value)
* - rmdir: delete a directory if empty ($data has 1 value)
* - installed_as: mark a file as installed ($data has 4 values).
* @var array $data For all file operations, this array must contain the
* full path to the file or directory that is being operated on. For
* the rename command, the first parameter must be the file to rename,
* the second its new name.
*
* The installed_as operation contains 4 elements in this order:
* 1. Filename as listed in the filelist element from package.xml
* 2. Full path to the installed file
* 3. Full path from the php_dir configuration variable used in this
* installation
* 4. Relative path from the php_dir that this file is installed in
*/
function addFileOperation($type, $data)
{
if (!is_array($data)) {
return $this->raiseError('Internal Error: $data in addFileOperation'
. ' must be an array, was ' . gettype($data));
}
if ($type == 'chmod') {
$octmode = decoct($data[0]);
$this->log(3, "adding to transaction: $type $octmode $data[1]");
} else {
$this->log(3, "adding to transaction: $type " . implode(" ", $data));
}
$this->file_operations[] = array($type, $data);
}
// }}}
// {{{ startFileTransaction()
function startFileTransaction($rollback_in_case = false)
{
if (count($this->file_operations) && $rollback_in_case) {
$this->rollbackFileTransaction();
}
$this->file_operations = array();
}
// }}}
// {{{ commitFileTransaction()
function commitFileTransaction()
{
$n = count($this->file_operations);
$this->log(2, "about to commit $n file operations");
2003-10-05 16:42:18 +00:00
// {{{ first, check permissions and such manually
$errors = array();
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
if (!file_exists($data[0])) {
$errors[] = "cannot rename file $data[0], doesn't exist";
}
// check that dest dir. is writable
if (!is_writable(dirname($data[1]))) {
$errors[] = "permission denied ($type): $data[1]";
}
break;
case 'chmod':
// check that file is writable
2003-09-17 03:22:55 +00:00
if (!is_writable($data[1])) {
$errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
}
break;
case 'delete':
if (!file_exists($data[0])) {
$this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
}
// check that directory is writable
if (file_exists($data[0]) && !is_writable(dirname($data[0]))) {
$errors[] = "permission denied ($type): $data[0]";
}
break;
}
}
2003-10-05 16:42:18 +00:00
// }}}
$m = sizeof($errors);
if ($m > 0) {
foreach ($errors as $error) {
$this->log(1, $error);
}
return false;
}
2003-10-05 16:42:18 +00:00
// {{{ really commit the transaction
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
2003-09-04 22:21:33 +00:00
@unlink($data[1]);
@rename($data[0], $data[1]);
$this->log(3, "+ mv $data[0] $data[1]");
break;
case 'chmod':
2003-09-17 03:22:55 +00:00
@chmod($data[1], $data[0]);
$octmode = decoct($data[0]);
$this->log(3, "+ chmod $octmode $data[1]");
break;
case 'delete':
@unlink($data[0]);
$this->log(3, "+ rm $data[0]");
break;
case 'rmdir':
@rmdir($data[0]);
$this->log(3, "+ rmdir $data[0]");
break;
case 'installed_as':
$this->pkginfo['filelist'][$data[0]]['installed_as'] = $data[1];
if (!isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
$this->pkginfo['filelist']['dirtree'][dirname($data[1])] = true;
while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
&& $data[3] != '.') {
$this->pkginfo['filelist']['dirtree']
[$this->_prependPath($data[3], $data[2])] = true;
$data[3] = dirname($data[3]);
}
}
break;
}
}
2003-10-05 16:42:18 +00:00
// }}}
$this->log(2, "successfully committed $n file operations");
$this->file_operations = array();
return true;
}
// }}}
// {{{ rollbackFileTransaction()
function rollbackFileTransaction()
{
$n = count($this->file_operations);
$this->log(2, "rolling back $n file operations");
foreach ($this->file_operations as $tr) {
list($type, $data) = $tr;
switch ($type) {
case 'rename':
@unlink($data[0]);
$this->log(3, "+ rm $data[0]");
break;
case 'mkdir':
@rmdir($data[0]);
$this->log(3, "+ rmdir $data[0]");
break;
case 'chmod':
break;
case 'delete':
break;
2003-09-18 04:32:56 +00:00
case 'installed_as':
unset($this->pkginfo['filelist'][$data[0]]['installed_as']);
if (isset($this->pkginfo['filelist']['dirtree'][dirname($data[1])])) {
unset($this->pkginfo['filelist']['dirtree'][dirname($data[1])]);
while(!empty($data[3]) && $data[3] != '/' && $data[3] != '\\'
&& $data[3] != '.') {
unset($this->pkginfo['filelist']['dirtree']
[$this->_prependPath($data[3], $data[2])]);
$data[3] = dirname($data[3]);
}
}
if (isset($this->pkginfo['filelist']['dirtree'])
&& !count($this->pkginfo['filelist']['dirtree'])) {
2003-09-18 04:32:56 +00:00
unset($this->pkginfo['filelist']['dirtree']);
}
break;
}
}
$this->file_operations = array();
}
// }}}
// {{{ getPackageDownloadUrl()
function getPackageDownloadUrl($package, $version = null)
{
if ($version) {
$package .= "-$version";
}
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;
}
// }}}
// {{{ mkDirHier($dir)
function mkDirHier($dir)
{
$this->addFileOperation('mkdir', array($dir));
return parent::mkDirHier($dir);
}
// }}}
// {{{ _prependPath($path, $prepend)
function _prependPath($path, $prepend)
{
2003-01-29 21:42:54 +00:00
if (strlen($prepend) > 0) {
if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
$path = $prepend . substr($path, 2);
} else {
$path = $prepend . $path;
}
}
return $path;
}
// }}}
2003-10-05 16:42:18 +00:00
// {{{ extractDownloadFileName($pkgfile, &$version)
function extractDownloadFileName($pkgfile, &$version)
{
if (@is_file($pkgfile)) {
return $pkgfile;
}
// regex defined in Common.php
if (preg_match(PEAR_COMMON_PACKAGE_DOWNLOAD_PREG, $pkgfile, $m)) {
$version = (isset($m[3])) ? $m[3] : null;
return $m[1];
}
$version = null;
return $pkgfile;
}
// }}}
// {{{ _downloadFile()
/**
* @param string filename to download
* @param PEAR_Config Configuration object
* @param array options returned from Console_GetOpt
* @param array empty array to populate with error messages, if any
* @param string version/state
* @param string original value passed to command-line
* @param string preferred state (snapshot/devel/alpha/beta/stable)
* @return null|PEAR_Error|string
* @access private
*/
function _downloadFile($pkgfile, &$config, $options, &$errors, $version,
$origpkgfile, $state)
{
2003-10-05 16:42:18 +00:00
// {{{ check the package filename, and whether it's already installed
$need_download = false;
if (preg_match('#^(http|ftp)://#', $pkgfile)) {
$need_download = true;
} elseif (!@is_file($pkgfile)) {
if ($this->validPackageName($pkgfile)) {
if ($this->registry->packageExists($pkgfile)) {
if (empty($options['upgrade']) && empty($options['force'])) {
$errors[] = "$pkgfile already installed";
return;
}
}
$pkgfile = $this->getPackageDownloadUrl($pkgfile, $version);
$need_download = true;
} else {
if (strlen($pkgfile)) {
$errors[] = "Could not open the package file: $pkgfile";
} else {
$errors[] = "No package file given";
}
return;
}
}
2003-10-05 16:42:18 +00:00
// }}}
2003-10-05 16:42:18 +00:00
// {{{ Download package -----------------------------------------------
if ($need_download) {
$downloaddir = $config->get('download_dir');
if (empty($downloaddir)) {
if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
return $downloaddir;
}
2003-09-11 14:57:10 +00:00
$this->log(3, '+ tmp dir created at ' . $downloaddir);
}
$callback = $this->ui ? array(&$this, '_downloadCallback') : null;
$this->pushErrorHandling(PEAR_ERROR_RETURN);
$file = $this->downloadHttp($pkgfile, $this->ui, $downloaddir, $callback);
$this->popErrorHandling();
if (PEAR::isError($file)) {
if ($this->validPackageName($origpkgfile)) {
include_once 'PEAR/Remote.php';
$remote = new PEAR_Remote($config);
if (!PEAR::isError($info = $remote->call('package.info',
$origpkgfile))) {
if (!count($info['releases'])) {
return $this->raiseError('Package ' . $origpkgfile .
' has no releases');
} else {
return $this->raiseError('No releases of preferred state "'
. $state . '" exist for package ' . $origpkgfile .
'. Use ' . $origpkgfile . '-state to install another' .
' state (like ' . $origpkgfile .'-beta)',
PEAR_INSTALLER_ERROR_NO_PREF_STATE);
}
} else {
return $pkgfile;
}
} else {
return $this->raiseError($file);
}
}
$pkgfile = $file;
}
2003-10-05 16:42:18 +00:00
// }}}
return $pkgfile;
}
// }}}
// {{{ download()
/**
* Download any files and their dependencies, if necessary
*
* @param array a mixed list of package names, local files, or package.xml
* @param PEAR_Config
* @param array options from the command line
* @param array this is the array that will be populated with packages to
* install. Format of each entry:
*
* <code>
* array('pkg' => 'package_name', 'file' => '/path/to/local/file',
* 'info' => array() // parsed package.xml
* );
* </code>
* @param array this will be populated with any error messages
* @param false private recursion variable
* @param false private recursion variable
* @param false private recursion variable
*/
function download($packages, $options, &$config, &$installpackages,
&$errors, $installed = false, $willinstall = false, $state = false)
{
// recognized options:
// - onlyreqdeps : install all required dependencies as well
// - alldeps : install all dependencies, including optional
//
2003-10-05 16:42:18 +00:00
// {{{ determine preferred state, installroot, etc
if (!$willinstall) {
$willinstall = array();
}
if (!$state) {
$state = $config->get('preferred_state');
if (!$state) {
// don't inadvertantly use a non-set preferred_state
$state = null;
}
}
$mywillinstall = array();
$php_dir = $config->get('php_dir');
if (isset($options['installroot'])) {
if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
$options['installroot'] = substr($options['installroot'], 0, -1);
}
$php_dir = $this->_prependPath($php_dir, $options['installroot']);
$this->installroot = $options['installroot'];
} else {
$this->installroot = '';
}
2003-10-05 16:42:18 +00:00
// }}}
$this->registry = &new PEAR_Registry($php_dir);
2003-10-05 16:42:18 +00:00
// {{{ download files in this list if necessary
foreach($packages as $pkgfile) {
if (!is_file($pkgfile)) {
$origpkgfile = $pkgfile;
$pkgfile = $this->extractDownloadFileName($pkgfile, $version);
if (!$this->validPackageName($pkgfile)) {
return $this->raiseError("Package name '$pkgfile' not valid");
}
// ignore packages that are installed unless we are upgrading
$curinfo = $this->registry->packageInfo($pkgfile);
if ($this->registry->packageExists($pkgfile) && empty($options['upgrade']) && empty($options['force'])) {
$this->log(0, "Package '{$curinfo['package']}' already installed, skipping");
continue;
}
// Retrieve remote release list
include_once 'PEAR/Remote.php';
$curver = $curinfo['version'];
$remote = &new PEAR_Remote($config);
$releases = $remote->call('package.info', $pkgfile, 'releases');
if (!count($releases)) {
return $this->raiseError("No releases found for package '$pkgfile'");
}
// Want a specific version/state
if ($version !== null) {
// Passed Foo-1.2
if ($this->validPackageVersion($version)) {
if (!isset($releases[$version])) {
return $this->raiseError("No release with version '$version' found for '$pkgfile'");
2003-10-04 17:16:35 +00:00
}
// Passed Foo-alpha
} elseif (in_array($version, $this->getReleaseStates())) {
$state = $version;
$version = 0;
2003-10-04 17:16:35 +00:00
foreach ($releases as $ver => $inf) {
if ($inf['state'] == $state && version_compare("$version", "$ver") < 0) {
$version = $ver;
}
}
if ($version == 0) {
return $this->raiseError("No release with state '$state' found for '$pkgfile'");
}
// invalid postfix passed
} else {
return $this->raiseError("Invalid postfix '-$version', be sure to pass a valid PEAR ".
"version number or release state");
}
// Guess what to download
} else {
$states = $this->betterStates($state, true);
$possible = false;
$version = 0;
foreach ($releases as $ver => $inf) {
if (in_array($inf['state'], $states) && version_compare("$version", "$ver") < 0) {
$version = $ver;
}
}
if ($version == 0) {
return $this->raiseError('No release with state equal to: \'' . implode(', ', $states) .
"' found for '$pkgfile'");
}
}
// Check if we haven't already the version
if (empty($options['force'])) {
if ($curinfo['version'] == $version) {
$this->log(0, "Package '{$curinfo['package']}-{$curinfo['version']}' already installed, skipping");
continue;
} elseif (version_compare("$version", "{$curinfo['version']}") < 0) {
$this->log(0, "Already got '{$curinfo['package']}-{$curinfo['version']}' greater than requested '$version', skipping");
continue;
}
}
$pkgfile = $this->_downloadFile($pkgfile, $config, $options,
$errors, $version, $origpkgfile,
$state);
if (PEAR::isError($pkgfile)) {
return $pkgfile;
}
} // end is_file()
$tempinfo = $this->infoFromAny($pkgfile);
if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) {
// ignore dependencies if there are any errors
if (!PEAR::isError($tempinfo)) {
$mywillinstall[strtolower($tempinfo['package'])] = @$tempinfo['release_deps'];
}
}
$installpackages[] = array('pkg' => $tempinfo['package'],
'file' => $pkgfile, 'info' => $tempinfo);
} // end foreach($packages)
2003-10-05 16:42:18 +00:00
// }}}
2003-10-05 16:42:18 +00:00
// {{{ extract dependencies from downloaded files and then download
// them if necessary
if (isset($options['alldeps']) || isset($options['onlyreqdeps'])) {
include_once "PEAR/Remote.php";
$remote = new PEAR_Remote($config);
if (!$installed) {
$installed = $this->registry->listPackages();
array_walk($installed, create_function('&$v,$k','$v = strtolower($v);'));
$installed = array_flip($installed);
}
$deppackages = array();
2003-10-05 16:42:18 +00:00
// {{{ construct the list of dependencies for each file
foreach ($mywillinstall as $package => $alldeps) {
if (!is_array($alldeps)) {
continue;
}
foreach($alldeps as $info) {
if ($info['type'] != 'pkg') {
continue;
}
if (!isset($options['alldeps']) && isset($info['optional']) &&
$info['optional'] == 'yes') {
// skip optional deps
$this->log(0, "skipping Package $package optional dependency $info[name]");
continue;
}
2003-10-05 16:42:18 +00:00
// {{{ get releases
$releases = $remote->call('package.info', $info['name'], 'releases');
if (PEAR::isError($releases)) {
return $releases;
}
if (!count($releases)) {
if (!isset($installed[strtolower($info['name'])])) {
$errors[] = "Package $package dependency $info[name] ".
"has no releases";
}
continue;
}
$found = false;
$save = $releases;
while(count($releases) && !$found) {
if (!empty($state) && $state != 'any') {
list($release_version,$release) = each($releases);
if ($state != $release['state'] &&
!in_array($release['state'], $this->betterStates($state)))
{
// drop this release - it ain't stable enough
array_shift($releases);
} else {
$found = true;
}
} else {
$found = true;
}
}
if (!count($releases) && !$found) {
$get = array();
foreach($save as $release) {
$get = array_merge($get,
$this->betterStates($release['state'], true));
}
$savestate = array_shift($get);
$errors[] = "Release for $package dependency $info[name] " .
"has state '$savestate', requires $state";
continue;
}
if (in_array(strtolower($info['name']), $willinstall) ||
isset($mywillinstall[strtolower($info['name'])])) {
// skip upgrade check for packages we will install
continue;
}
if (!isset($installed[strtolower($info['name'])])) {
// check to see if we can install the specific version required
if ($info['rel'] == 'eq') {
$deppackages[] = $info['name'] . '-' . $info['version'];
continue;
}
// skip upgrade check for packages we don't have installed
$deppackages[] = $info['name'];
continue;
}
2003-10-05 16:42:18 +00:00
// }}}
2003-10-05 16:42:18 +00:00
// {{{ see if a dependency must be upgraded
$inst_version = $this->registry->packageInfo($info['name'], 'version');
if (!isset($info['version'])) {
// this is a rel='has' dependency, check against latest
if (version_compare($release_version, $inst_version, 'le')) {
continue;
} else {
$deppackages[] = $info['name'];
continue;
}
}
if (version_compare($info['version'], $inst_version, 'le')) {
// installed version is up-to-date
continue;
}
$deppackages[] = $info['name'];
2003-10-05 16:42:18 +00:00
// }}}
} // foreach($alldeps
2003-10-05 16:42:18 +00:00
}
// }}} foreach($willinstall
if (count($deppackages)) {
// check dependencies' dependencies
// combine the list of packages to install
$temppack = array();
foreach($installpackages as $p) {
$temppack[] = strtolower($p['info']['package']);
}
foreach($deppackages as $pack) {
$temppack[] = strtolower($pack);
}
$willinstall = array_merge($willinstall, $temppack);
$this->download($deppackages, $options, $config, $installpackages,
$errors, $installed, $willinstall, $state);
}
2003-10-05 16:42:18 +00:00
} // }}} if --alldeps or --onlyreqdeps
}
// }}}
// {{{ install()
/**
* Installs the files within the package file specified.
*
* @param string $pkgfile path to the package file
* @param array $options
* recognized options:
* - installroot : optional prefix directory for installation
* - force : force installation
* - register-only : update registry but don't install files
* - upgrade : upgrade existing install
* - soft : fail silently
* - nodeps : ignore dependency conflicts/missing dependencies
* - alldeps : install all dependencies
* - onlyreqdeps : install only required dependencies
*
* @return array|PEAR_Error package info if successful
*/
function install($pkgfile, $options = array())
{
$php_dir = $this->config->get('php_dir');
if (isset($options['installroot'])) {
if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
$options['installroot'] = substr($options['installroot'], 0, -1);
}
$php_dir = $this->_prependPath($php_dir, $options['installroot']);
$this->installroot = $options['installroot'];
} else {
$this->installroot = '';
}
$this->registry = &new PEAR_Registry($php_dir);
2002-04-25 09:13:55 +00:00
// ==> XXX should be removed later on
$flag_old_format = false;
if (substr($pkgfile, -4) == '.xml') {
$descfile = $pkgfile;
} else {
2003-10-05 16:42:18 +00:00
// {{{ Decompress pack in tmp dir -------------------------------------
// To allow relative package file names
$pkgfile = realpath($pkgfile);
if (PEAR::isError($tmpdir = System::mktemp('-d'))) {
return $tmpdir;
}
2003-09-11 14:57:10 +00:00
$this->log(3, '+ tmp dir created at ' . $tmpdir);
2002-05-27 12:45:03 +00:00
$tar = new Archive_Tar($pkgfile);
2003-08-05 12:28:57 +00:00
if (!@$tar->extract($tmpdir)) {
return $this->raiseError("unable to unpack $pkgfile");
}
2002-02-13 01:40:18 +00:00
2003-10-05 16:42:18 +00:00
// {{{ 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");
}
2003-10-05 16:42:18 +00:00
// }}}
// <== XXX This part should be removed later on
2003-10-05 16:42:18 +00:00
// }}}
}
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;
}
$this->validatePackageInfo($pkginfo, $errors, $warnings);
2002-09-09 21:41:32 +00:00
// XXX We allow warnings, do we have to do it?
if (count($errors)) {
if (empty($options['force'])) {
return $this->raiseError("The following errors where found (use force option to install anyway):\n".
implode("\n", $errors));
} else {
$this->log(0, "warning : the following errors were found:\n".
implode("\n", $errors));
}
}
2001-12-12 01:30:56 +00:00
$pkgname = $pkginfo['package'];
2003-10-05 16:42:18 +00:00
// {{{ Check dependencies -------------------------------------------
if (isset($pkginfo['release_deps']) && empty($options['nodeps'])) {
$dep_errors = '';
$error = $this->checkDeps($pkginfo, $dep_errors);
if ($error == true) {
if (empty($options['soft'])) {
$this->log(0, substr($dep_errors, 1));
}
return $this->raiseError("$pkgname: Dependencies failed");
} else if (!empty($dep_errors)) {
// Print optional dependencies
if (empty($options['soft'])) {
$this->log(0, $dep_errors);
}
}
}
2003-10-05 16:42:18 +00:00
// }}}
2003-10-05 16:42:18 +00:00
// {{{ checks to do when not in "force" mode
if (empty($options['force'])) {
$test = $this->registry->checkFileMap($pkginfo);
if (sizeof($test)) {
$tmp = $test;
foreach ($tmp as $file => $pkg) {
if ($pkg == $pkgname) {
unset($test[$file]);
}
}
if (sizeof($test)) {
$msg = "$pkgname: conflicting files found:\n";
$longest = max(array_map("strlen", array_keys($test)));
$fmt = "%${longest}s (%s)\n";
foreach ($test as $file => $pkg) {
$msg .= sprintf($fmt, $file, $pkg);
}
return $this->raiseError($msg);
}
}
}
2003-10-05 16:42:18 +00:00
// }}}
$this->startFileTransaction();
2001-12-12 01:30:56 +00:00
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 {
if ($this->registry->packageExists($pkgname)) {
$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);
}
}
2001-12-12 01:30:56 +00:00
}
}
2003-10-05 16:42:18 +00:00
// {{{ Copy files to dest dir ---------------------------------------
// info from the package it self we want to access from _installFile
$this->pkginfo = &$pkginfo;
2002-06-02 13:07:19 +00:00
// used to determine whether we should build any C code
$this->source_files = 0;
if (empty($options['register-only'])) {
if (!is_dir($php_dir)) {
return $this->raiseError("no script destination directory\n",
null, PEAR_ERROR_DIE);
2001-08-01 13:24:55 +00:00
}
$tmp_path = dirname($descfile);
if (substr($pkgfile, -4) != '.xml') {
$tmp_path .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkginfo['version'];
}
// ==> XXX This part should be removed later on
if ($flag_old_format) {
$tmp_path = dirname($descfile);
}
// <== XXX This part should be removed later on
2003-10-05 16:42:18 +00:00
// {{{ install files
foreach ($pkginfo['filelist'] as $file => $atts) {
$this->expectError(PEAR_INSTALLER_FAILED);
$res = $this->_installFile($file, $atts, $tmp_path, $options);
$this->popExpect();
if (PEAR::isError($res)) {
if (empty($options['ignore-errors'])) {
$this->rollbackFileTransaction();
if ($res->getMessage() == "file does not exist") {
$this->raiseError("file $file in package.xml does not exist");
}
return $this->raiseError($res);
} else {
$this->log(0, "Warning: " . $res->getMessage());
}
}
if ($res != PEAR_INSTALLER_OK) {
// Do not register files that were not installed
unset($pkginfo['filelist'][$file]);
}
2001-08-01 13:24:55 +00:00
}
2003-10-05 16:42:18 +00:00
// }}}
2002-06-02 13:07:19 +00:00
2003-10-05 16:42:18 +00:00
// {{{ compile and install source files
if ($this->source_files > 0 && empty($options['nobuild'])) {
2002-06-02 13:07:19 +00:00
$this->log(1, "$this->source_files source files, building");
2002-11-08 00:19:21 +00:00
$bob = &new PEAR_Builder($this->ui);
2002-06-02 13:07:19 +00:00
$bob->debug = $this->debug;
$built = $bob->build($descfile, array(&$this, '_buildCallback'));
if (PEAR::isError($built)) {
$this->rollbackFileTransaction();
2002-06-02 13:07:19 +00:00
return $built;
}
2003-08-05 14:31:15 +00:00
$this->log(1, "\nBuild process completed successfully");
2002-06-02 13:07:19 +00:00
foreach ($built as $ext) {
$bn = basename($ext['file']);
list($_ext_name, ) = explode('.', $bn);
if (extension_loaded($_ext_name)) {
$this->raiseError("Extension '$_ext_name' already loaded. Please unload it ".
"in your php.ini file prior to install or upgrade it.");
}
$dest = $this->config->get('ext_dir') . DIRECTORY_SEPARATOR . $bn;
2003-08-05 14:31:15 +00:00
$this->log(1, "Installing '$bn' at ext_dir ($dest)");
$this->log(3, "+ cp $ext[file] ext_dir ($dest)");
2002-11-08 00:19:21 +00:00
$copyto = $this->_prependPath($dest, $this->installroot);
if (!@copy($ext['file'], $copyto)) {
$this->rollbackFileTransaction();
return $this->raiseError("failed to copy $bn to $copyto");
2002-06-02 13:07:19 +00:00
}
$pkginfo['filelist'][$bn] = array(
'role' => 'ext',
'installed_as' => $dest,
'php_api' => $ext['php_api'],
'zend_mod_api' => $ext['zend_mod_api'],
'zend_ext_api' => $ext['zend_ext_api'],
);
}
}
2003-10-05 16:42:18 +00:00
// }}}
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
}
2003-10-05 16:42:18 +00:00
// }}}
$ret = false;
2003-10-05 16:42:18 +00:00
// {{{ Register that the package is installed -----------------------
2001-12-12 01:30:56 +00:00
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);
2001-12-12 01:30:56 +00:00
} else {
// new: upgrade installs a package if it isn't installed
if (!$this->registry->packageExists($pkgname)) {
$ret = $this->registry->addPackage($pkgname, $pkginfo);
} else {
$ret = $this->registry->updatePackage($pkgname, $pkginfo, false);
}
2001-12-12 01:30:56 +00:00
}
if (!$ret) {
return $this->raiseError("Adding package $pkgname to registry failed");
}
2003-10-05 16:42:18 +00:00
// }}}
return $pkginfo;
}
// }}}
// {{{ uninstall()
2001-11-15 01:24:35 +00:00
/**
* Uninstall a package
*
* This method removes all files installed by the application, and then
* removes any empty directories.
* @param string package name
* @param array Command-line options. Possibilities include:
*
* - installroot: base installation dir, if not the default
*/
function uninstall($package, $options = array())
2001-11-15 01:24:35 +00:00
{
$php_dir = $this->config->get('php_dir');
if (isset($options['installroot'])) {
if (substr($options['installroot'], -1) == DIRECTORY_SEPARATOR) {
$options['installroot'] = substr($options['installroot'], 0, -1);
}
$this->installroot = $options['installroot'];
$php_dir = $this->_prependPath($php_dir, $this->installroot);
} else {
$this->installroot = '';
2001-11-15 01:24:35 +00:00
}
$this->registry = &new PEAR_Registry($php_dir);
$filelist = $this->registry->packageInfo($package, 'filelist');
if ($filelist == null) {
return $this->raiseError("$package not installed");
}
2003-07-08 10:33:38 +00:00
if (empty($options['nodeps'])) {
$depchecker = &new PEAR_Dependency($this->registry);
$error = $depchecker->checkPackageUninstall($errors, $warning, $package);
2003-07-08 10:33:38 +00:00
if ($error) {
return $this->raiseError($errors . 'uninstall failed');
}
if ($warning) {
$this->log(0, $warning);
}
2003-07-08 10:33:38 +00:00
}
2003-10-05 16:42:18 +00:00
// {{{ Delete the files
$this->startFileTransaction();
if (PEAR::isError($err = $this->_deletePackageFiles($package))) {
$this->rollbackFileTransaction();
return $this->raiseError($err);
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
return $this->raiseError("uninstall failed");
} else {
$this->startFileTransaction();
2003-09-18 04:32:56 +00:00
if (!isset($filelist['dirtree']) || !count($filelist['dirtree'])) {
return $this->registry->deletePackage($package);
}
// attempt to delete empty directories
uksort($filelist['dirtree'], array($this, '_sortDirs'));
foreach($filelist['dirtree'] as $dir => $notused) {
$this->addFileOperation('rmdir', array($dir));
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
}
}
2003-10-05 16:42:18 +00:00
// }}}
2001-12-12 01:30:56 +00:00
// Register that the package is no longer installed
return $this->registry->deletePackage($package);
2001-11-15 01:24:35 +00:00
}
// }}}
// {{{ _sortDirs()
function _sortDirs($a, $b)
{
if (strnatcmp($a, $b) == -1) return 1;
if (strnatcmp($a, $b) == 1) return -1;
return 0;
}
// }}}
2002-01-02 17:12:26 +00:00
// {{{ checkDeps()
/**
* Check if the package meets all dependencies
*
* @param array Package information (passed by reference)
* @param string Error message (passed by reference)
* @return boolean False when no error occured, otherwise true
*/
function checkDeps(&$pkginfo, &$errors)
{
if (empty($this->registry)) {
$this->registry = &new PEAR_Registry($this->config->get('php_dir'));
}
$depchecker = &new PEAR_Dependency($this->registry);
$error = $errors = '';
$failed_deps = $optional_deps = array();
if (is_array($pkginfo['release_deps'])) {
foreach($pkginfo['release_deps'] as $dep) {
$code = $depchecker->callCheckMethod($error, $dep);
if ($code) {
if (isset($dep['optional']) && $dep['optional'] == 'yes') {
/* die ugly hack die
// Ugly hack to adjust the error messages
$error = str_replace('requires ', '', $error);
$error = ucfirst($error);
$error = $error . ' is recommended to utilize some features.';*/
$optional_deps[] = array($dep, $code, $error);
} else {
$failed_deps[] = array($dep, $code, $error);
}
}
}
2003-10-05 16:42:18 +00:00
// {{{ failed dependencies
$n = count($failed_deps);
if ($n > 0) {
for ($i = 0; $i < $n; $i++) {
if (isset($failed_deps[$i]['type'])) {
$type = $failed_deps[$i]['type'];
} else {
$type = 'pkg';
}
switch ($failed_deps[$i][1]) {
case PEAR_DEPENDENCY_MISSING:
if ($type == 'pkg') {
// install
}
$errors .= "\n" . $failed_deps[$i][2];
break;
case PEAR_DEPENDENCY_UPGRADE_MINOR:
if ($type == 'pkg') {
// upgrade
}
$errors .= "\n" . $failed_deps[$i][2];
break;
default:
$errors .= "\n" . $failed_deps[$i][2];
break;
}
}
return true;
}
2003-10-05 16:42:18 +00:00
// }}}
2003-10-05 16:42:18 +00:00
// {{{ optional dependencies
$count_optional = count($optional_deps);
if ($count_optional > 0) {
$errors = "Optional dependencies:";
for ($i = 0; $i < $count_optional; $i++) {
if (isset($optional_deps[$i]['type'])) {
$type = $optional_deps[$i]['type'];
} else {
$type = 'pkg';
}
switch ($optional_deps[$i][1]) {
case PEAR_DEPENDENCY_MISSING:
case PEAR_DEPENDENCY_UPGRADE_MINOR:
default:
$errors .= "\n" . $optional_deps[$i][2];
break;
}
}
return false;
}
2003-10-05 16:42:18 +00:00
// }}}
}
return false;
}
2002-01-02 17:12:26 +00:00
// }}}
// {{{ _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;
2003-09-11 14:47:03 +00:00
case 'bytesread':
static $bytes;
if (empty($bytes)) {
$bytes = 0;
}
if (!($bytes % 10240)) {
$this->log(1, '.', false);
}
$bytes += $params;
break;
case 'start':
$this->log(1, "Starting to download {$params[0]} (".number_format($params[1], 0, '', ',')." bytes)");
break;
}
2002-06-02 13:07:19 +00:00
if (method_exists($this->ui, '_downloadCallback'))
$this->ui->_downloadCallback($msg, $params);
}
// }}}
// {{{ _buildCallback()
function _buildCallback($what, $data)
{
if (($what == 'cmdoutput' && $this->debug > 1) ||
($what == 'output' && $this->debug > 0)) {
$this->ui->outputData(rtrim($data), 'build');
}
}
2002-01-02 17:12:26 +00:00
// }}}
}
2003-10-05 16:42:18 +00:00
// {{{ md5_file() utility function
2002-10-12 00:40:16 +00:00
if (!function_exists("md5_file")) {
function md5_file($filename) {
$fp = fopen($filename, "r");
if (!$fp) return null;
$contents = fread($fp, filesize($filename));
fclose($fp);
return md5($contents);
}
}
2003-10-05 16:42:18 +00:00
// }}}
2002-10-12 00:40:16 +00:00
?>