* @author Tomas V.V. Cox
* array('pkg' => 'package_name', 'file' => '/path/to/local/file',
* 'info' => array() // parsed package.xml
* );
*
* @param array this will be populated with any error messages
* @param false private recursion variable
* @param false private recursion variable
* @param false private recursion variable
* @deprecated in favor of PEAR_Downloader
*/
function download($packages, $options, &$config, &$installpackages,
&$errors, $installed = false, $willinstall = false, $state = false)
{
// trickiness: initialize here
parent::PEAR_Downloader($this->ui, $options, $config);
$ret = parent::download($packages);
$errors = $this->getErrorMsgs();
$installpackages = $this->getDownloadedPackages();
trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
"in favor of PEAR_Downloader class", E_USER_WARNING);
return $ret;
}
// }}}
// {{{ _parsePackageXml()
function _parsePackageXml(&$descfile)
{
// Parse xml file -----------------------------------------------
$pkg = new PEAR_PackageFile($this->config, $this->debug);
PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
$p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
PEAR::staticPopErrorHandling();
if (PEAR::isError($p)) {
if (is_array($p->getUserInfo())) {
foreach ($p->getUserInfo() as $err) {
$loglevel = $err['level'] == 'error' ? 0 : 1;
if (!isset($this->_options['soft'])) {
$this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
}
}
}
return $this->raiseError('Installation failed: invalid package file');
}
$descfile = $p->getPackageFile();
return $p;
}
// }}}
/**
* Set the list of PEAR_Downloader_Package objects to allow more sane
* dependency validation
* @param array
*/
function setDownloadedPackages(&$pkgs)
{
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
$err = $this->analyzeDependencies($pkgs);
PEAR::popErrorHandling();
if (PEAR::isError($err)) {
return $err;
}
$this->_downloadedPackages = &$pkgs;
}
/**
* Set the list of PEAR_Downloader_Package objects to allow more sane
* dependency validation
* @param array
*/
function setUninstallPackages(&$pkgs)
{
$this->_downloadedPackages = &$pkgs;
}
function getInstallPackages()
{
return $this->_downloadedPackages;
}
// {{{ install()
/**
* Installs the files within the package file specified.
*
* @param string|PEAR_Downloader_Package $pkgfile path to the package file,
* or a pre-initialized packagefile object
* @param array $options
* recognized options:
* - installroot : optional prefix directory for installation
* - force : force installation
* - register-only : update registry but don't install files
* - upgrade : upgrade existing install
* - soft : fail silently
* - nodeps : ignore dependency conflicts/missing dependencies
* - alldeps : install all dependencies
* - onlyreqdeps : install only required dependencies
*
* @return array|PEAR_Error package info if successful
*/
function install($pkgfile, $options = array())
{
$this->_options = $options;
$this->_registry = &$this->config->getRegistry();
if (is_object($pkgfile)) {
$dlpkg = &$pkgfile;
$pkg = $pkgfile->getPackageFile();
$pkgfile = $pkg->getArchiveFile();
$descfile = $pkg->getPackageFile();
} else {
$descfile = $pkgfile;
$pkg = $this->_parsePackageXml($descfile);
if (PEAR::isError($pkg)) {
return $pkg;
}
}
$tmpdir = dirname($descfile);
if (realpath($descfile) != realpath($pkgfile)) {
// Use the temp_dir since $descfile can contain the download dir path
$tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
$tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
$tar = new Archive_Tar($pkgfile);
if (!$tar->extract($tmpdir)) {
return $this->raiseError("unable to unpack $pkgfile");
}
}
$pkgname = $pkg->getName();
$channel = $pkg->getChannel();
if (isset($this->_options['packagingroot'])) {
$regdir = $this->_prependPath(
$this->config->get('php_dir', null, 'pear.php.net'),
$this->_options['packagingroot']);
$packrootphp_dir = $this->_prependPath(
$this->config->get('php_dir', null, $channel),
$this->_options['packagingroot']);
}
if (isset($options['installroot'])) {
$this->config->setInstallRoot($options['installroot']);
$this->_registry = &$this->config->getRegistry();
$installregistry = &$this->_registry;
$this->installroot = ''; // all done automagically now
$php_dir = $this->config->get('php_dir', null, $channel);
} else {
$this->config->setInstallRoot(false);
$this->_registry = &$this->config->getRegistry();
if (isset($this->_options['packagingroot'])) {
$installregistry = &new PEAR_Registry($regdir);
if (!$installregistry->channelExists($channel, true)) {
// we need to fake a channel-discover of this channel
$chanobj = $this->_registry->getChannel($channel, true);
$installregistry->addChannel($chanobj);
}
$php_dir = $packrootphp_dir;
} else {
$installregistry = &$this->_registry;
$php_dir = $this->config->get('php_dir', null, $channel);
}
$this->installroot = '';
}
// {{{ checks to do when not in "force" mode
if (empty($options['force']) &&
(file_exists($this->config->get('php_dir')) &&
is_dir($this->config->get('php_dir')))) {
$testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
$instfilelist = $pkg->getInstallationFileList(true);
if (PEAR::isError($instfilelist)) {
return $instfilelist;
}
// ensure we have the most accurate registry
$installregistry->flushFileMap();
$test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
if (PEAR::isError($test)) {
return $test;
}
if (sizeof($test)) {
$pkgs = $this->getInstallPackages();
$found = false;
foreach ($pkgs as $param) {
if ($pkg->isSubpackageOf($param)) {
$found = true;
break;
}
}
if ($found) {
// subpackages can conflict with earlier versions of parent packages
$parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
$tmp = $test;
foreach ($tmp as $file => $info) {
if (is_array($info)) {
if (strtolower($info[1]) == strtolower($param->getPackage()) &&
strtolower($info[0]) == strtolower($param->getChannel())
) {
if (isset($parentreg['filelist'][$file])) {
unset($parentreg['filelist'][$file]);
} else{
$pos = strpos($file, '/');
$basedir = substr($file, 0, $pos);
$file2 = substr($file, $pos + 1);
if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
&& $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
) {
unset($parentreg['filelist'][$file2]);
}
}
unset($test[$file]);
}
} else {
if (strtolower($param->getChannel()) != 'pear.php.net') {
continue;
}
if (strtolower($info) == strtolower($param->getPackage())) {
if (isset($parentreg['filelist'][$file])) {
unset($parentreg['filelist'][$file]);
} else{
$pos = strpos($file, '/');
$basedir = substr($file, 0, $pos);
$file2 = substr($file, $pos + 1);
if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
&& $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
) {
unset($parentreg['filelist'][$file2]);
}
}
unset($test[$file]);
}
}
}
$pfk = &new PEAR_PackageFile($this->config);
$parentpkg = &$pfk->fromArray($parentreg);
$installregistry->updatePackage2($parentpkg);
}
if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
$tmp = $test;
foreach ($tmp as $file => $info) {
if (is_string($info)) {
// pear.php.net packages are always stored as strings
if (strtolower($info) == strtolower($param->getPackage())) {
// upgrading existing package
unset($test[$file]);
}
}
}
}
if (count($test)) {
$msg = "$channel/$pkgname: conflicting files found:\n";
$longest = max(array_map("strlen", array_keys($test)));
$fmt = "%${longest}s (%s)\n";
foreach ($test as $file => $info) {
if (!is_array($info)) {
$info = array('pear.php.net', $info);
}
$info = $info[0] . '/' . $info[1];
$msg .= sprintf($fmt, $file, $info);
}
if (!isset($options['ignore-errors'])) {
return $this->raiseError($msg);
}
if (!isset($options['soft'])) {
$this->log(0, "WARNING: $msg");
}
}
}
}
// }}}
$this->startFileTransaction();
$usechannel = $channel;
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists($pkgname, $channel);
if (!$test) {
$test = $installregistry->packageExists($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
}
} else {
$test = $installregistry->packageExists($pkgname, $channel);
}
if (empty($options['upgrade']) && empty($options['soft'])) {
// checks to do only when installing new packages
if (empty($options['force']) && $test) {
return $this->raiseError("$channel/$pkgname is already installed");
}
} else {
// Upgrade
if ($test) {
$v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
$v2 = $pkg->getVersion();
$cmp = version_compare("$v1", "$v2", 'gt');
if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
}
}
}
// Do cleanups for upgrade and install, remove old release's files first
if ($test && empty($options['register-only'])) {
// when upgrading, remove old release's files first:
if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
true))) {
if (!isset($options['ignore-errors'])) {
return $this->raiseError($err);
}
if (!isset($options['soft'])) {
$this->log(0, 'WARNING: ' . $err->getMessage());
}
} else {
$backedup = $err;
}
}
// {{{ Copy files to dest dir ---------------------------------------
// info from the package it self we want to access from _installFile
$this->pkginfo = &$pkg;
// used to determine whether we should build any C code
$this->source_files = 0;
$savechannel = $this->config->get('default_channel');
if (empty($options['register-only']) && !is_dir($php_dir)) {
if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
return $this->raiseError("no installation destination directory '$php_dir'\n");
}
}
if (substr($pkgfile, -4) != '.xml') {
$tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
}
$this->configSet('default_channel', $channel);
// {{{ install files
$ver = $pkg->getPackagexmlVersion();
if (version_compare($ver, '2.0', '>=')) {
$filelist = $pkg->getInstallationFilelist();
} else {
$filelist = $pkg->getFileList();
}
if (PEAR::isError($filelist)) {
return $filelist;
}
$p = &$installregistry->getPackage($pkgname, $channel);
$dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
$pkg->resetFilelist();
$pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
'version', $pkg->getChannel()));
foreach ($filelist as $file => $atts) {
$this->expectError(PEAR_INSTALLER_FAILED);
if ($pkg->getPackagexmlVersion() == '1.0') {
$res = $this->_installFile($file, $atts, $tmpdir, $options);
} else {
$res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
}
$this->popExpect();
if (PEAR::isError($res)) {
if (empty($options['ignore-errors'])) {
$this->rollbackFileTransaction();
if ($res->getMessage() == "file does not exist") {
$this->raiseError("file $file in package.xml does not exist");
}
return $this->raiseError($res);
}
if (!isset($options['soft'])) {
$this->log(0, "Warning: " . $res->getMessage());
}
}
$real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
// Register files that were installed
$pkg->installedFile($file, $atts);
}
}
// }}}
// {{{ compile and install source files
if ($this->source_files > 0 && empty($options['nobuild'])) {
if (PEAR::isError($err =
$this->_compileSourceFiles($savechannel, $pkg))) {
return $err;
}
}
// }}}
if (isset($backedup)) {
$this->_removeBackups($backedup);
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
$this->configSet('default_channel', $savechannel);
return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
}
// }}}
$ret = false;
$installphase = 'install';
$oldversion = false;
// {{{ Register that the package is installed -----------------------
if (empty($options['upgrade'])) {
// if 'force' is used, replace the info in registry
$usechannel = $channel;
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists($pkgname, $channel);
if (!$test) {
$test = $installregistry->packageExists($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
}
} else {
$test = $installregistry->packageExists($pkgname, $channel);
}
if (!empty($options['force']) && $test) {
$oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
$installregistry->deletePackage($pkgname, $usechannel);
}
$ret = $installregistry->addPackage2($pkg);
} else {
if ($dirtree) {
$this->startFileTransaction();
// attempt to delete empty directories
uksort($dirtree, array($this, '_sortDirs'));
foreach($dirtree as $dir => $notused) {
$this->addFileOperation('rmdir', array($dir));
}
$this->commitFileTransaction();
}
$usechannel = $channel;
if ($channel == 'pecl.php.net') {
$test = $installregistry->packageExists($pkgname, $channel);
if (!$test) {
$test = $installregistry->packageExists($pkgname, 'pear.php.net');
$usechannel = 'pear.php.net';
}
} else {
$test = $installregistry->packageExists($pkgname, $channel);
}
// new: upgrade installs a package if it isn't installed
if (!$test) {
$ret = $installregistry->addPackage2($pkg);
} else {
if ($usechannel != $channel) {
$installregistry->deletePackage($pkgname, $usechannel);
$ret = $installregistry->addPackage2($pkg);
} else {
$ret = $installregistry->updatePackage2($pkg);
}
$installphase = 'upgrade';
}
}
if (!$ret) {
$this->configSet('default_channel', $savechannel);
return $this->raiseError("Adding package $channel/$pkgname to registry failed");
}
// }}}
$this->configSet('default_channel', $savechannel);
if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
if (PEAR_Task_Common::hasPostinstallTasks()) {
PEAR_Task_Common::runPostinstallTasks($installphase);
}
}
return $pkg->toArray(true);
}
// }}}
// {{{ _compileSourceFiles()
/**
* @param string
* @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
*/
function _compileSourceFiles($savechannel, &$filelist)
{
require_once 'PEAR/Builder.php';
$this->log(1, "$this->source_files source files, building");
$bob = &new PEAR_Builder($this->ui);
$bob->debug = $this->debug;
$built = $bob->build($filelist, array(&$this, '_buildCallback'));
if (PEAR::isError($built)) {
$this->rollbackFileTransaction();
$this->configSet('default_channel', $savechannel);
return $built;
}
$this->log(1, "\nBuild process completed successfully");
foreach ($built as $ext) {
$bn = basename($ext['file']);
list($_ext_name, $_ext_suff) = explode('.', $bn);
if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
if (extension_loaded($_ext_name)) {
$this->raiseError("Extension '$_ext_name' already loaded. " .
'Please unload it in your php.ini file ' .
'prior to install or upgrade');
}
$role = 'ext';
} else {
$role = 'src';
}
$dest = $ext['dest'];
$packagingroot = '';
if (isset($this->_options['packagingroot'])) {
$packagingroot = $this->_options['packagingroot'];
}
$copyto = $this->_prependPath($dest, $packagingroot);
$extra = $copyto != $dest ? " as '$copyto'" : '';
$this->log(1, "Installing '$dest'$extra");
$copydir = dirname($copyto);
// pretty much nothing happens if we are only registering the install
if (empty($this->_options['register-only'])) {
if (!file_exists($copydir) || !is_dir($copydir)) {
if (!$this->mkDirHier($copydir)) {
return $this->raiseError("failed to mkdir $copydir",
PEAR_INSTALLER_FAILED);
}
$this->log(3, "+ mkdir $copydir");
}
if (!@copy($ext['file'], $copyto)) {
return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
}
$this->log(3, "+ cp $ext[file] $copyto");
$this->addFileOperation('rename', array($ext['file'], $copyto));
if (!OS_WINDOWS) {
$mode = 0666 & ~(int)octdec($this->config->get('umask'));
$this->addFileOperation('chmod', array($mode, $copyto));
if (!@chmod($copyto, $mode)) {
$this->log(0, "failed to change mode of $copyto ($php_errormsg)");
}
}
}
$data = array(
'role' => $role,
'name' => $bn,
'installed_as' => $dest,
'php_api' => $ext['php_api'],
'zend_mod_api' => $ext['zend_mod_api'],
'zend_ext_api' => $ext['zend_ext_api'],
);
if ($filelist->getPackageXmlVersion() == '1.0') {
$filelist->installedFile($bn, $data);
} else {
$filelist->installedFile($bn, array('attribs' => $data));
}
}
}
// }}}
function &getUninstallPackages()
{
return $this->_downloadedPackages;
}
// {{{ uninstall()
/**
* Uninstall a package
*
* This method removes all files installed by the application, and then
* removes any empty directories.
* @param string package name
* @param array Command-line options. Possibilities include:
*
* - installroot: base installation dir, if not the default
* - register-only : update registry but don't remove files
* - nodeps: do not process dependencies of other packages to ensure
* uninstallation does not break things
*/
function uninstall($package, $options = array())
{
$installRoot = isset($options['installroot']) ? $options['installroot'] : '';
$this->config->setInstallRoot($installRoot);
$this->installroot = '';
$this->_registry = &$this->config->getRegistry();
if (is_object($package)) {
$channel = $package->getChannel();
$pkg = $package;
$package = $pkg->getPackage();
} else {
$pkg = false;
$info = $this->_registry->parsePackageName($package,
$this->config->get('default_channel'));
$channel = $info['channel'];
$package = $info['package'];
}
$savechannel = $this->config->get('default_channel');
$this->configSet('default_channel', $channel);
if (!is_object($pkg)) {
$pkg = $this->_registry->getPackage($package, $channel);
}
if (!$pkg) {
$this->configSet('default_channel', $savechannel);
return $this->raiseError($this->_registry->parsedPackageNameToString(
array(
'channel' => $channel,
'package' => $package
), true) . ' not installed');
}
if ($pkg->getInstalledBinary()) {
// this is just an alias for a binary package
return $this->_registry->deletePackage($package, $channel);
}
$filelist = $pkg->getFilelist();
PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
if (!class_exists('PEAR_Dependency2')) {
require_once 'PEAR/Dependency2.php';
}
$depchecker = &new PEAR_Dependency2($this->config, $options,
array('channel' => $channel, 'package' => $package),
PEAR_VALIDATE_UNINSTALLING);
$e = $depchecker->validatePackageUninstall($this);
PEAR::staticPopErrorHandling();
if (PEAR::isError($e)) {
if (!isset($options['ignore-errors'])) {
return $this->raiseError($e);
}
if (!isset($options['soft'])) {
$this->log(0, 'WARNING: ' . $e->getMessage());
}
} elseif (is_array($e)) {
if (!isset($options['soft'])) {
$this->log(0, $e[0]);
}
}
$this->pkginfo = &$pkg;
// pretty much nothing happens if we are only registering the uninstall
if (empty($options['register-only'])) {
// {{{ Delete the files
$this->startFileTransaction();
PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
PEAR::popErrorHandling();
$this->rollbackFileTransaction();
$this->configSet('default_channel', $savechannel);
if (!isset($options['ignore-errors'])) {
return $this->raiseError($err);
}
if (!isset($options['soft'])) {
$this->log(0, 'WARNING: ' . $err->getMessage());
}
} else {
PEAR::popErrorHandling();
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
if (!isset($options['ignore-errors'])) {
return $this->raiseError("uninstall failed");
}
if (!isset($options['soft'])) {
$this->log(0, 'WARNING: uninstall failed');
}
} else {
$this->startFileTransaction();
$dirtree = $pkg->getDirTree();
if ($dirtree === false) {
$this->configSet('default_channel', $savechannel);
return $this->_registry->deletePackage($package, $channel);
}
// attempt to delete empty directories
uksort($dirtree, array($this, '_sortDirs'));
foreach($dirtree as $dir => $notused) {
$this->addFileOperation('rmdir', array($dir));
}
if (!$this->commitFileTransaction()) {
$this->rollbackFileTransaction();
if (!isset($options['ignore-errors'])) {
return $this->raiseError("uninstall failed");
}
if (!isset($options['soft'])) {
$this->log(0, 'WARNING: uninstall failed');
}
}
}
// }}}
}
$this->configSet('default_channel', $savechannel);
// Register that the package is no longer installed
return $this->_registry->deletePackage($package, $channel);
}
/**
* Sort a list of arrays of array(downloaded packagefilename) by dependency.
*
* It also removes duplicate dependencies
* @param array an array of PEAR_PackageFile_v[1/2] objects
* @return array|PEAR_Error array of array(packagefilename, package.xml contents)
*/
function sortPackagesForUninstall(&$packages)
{
$this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
if (PEAR::isError($this->_dependencyDB)) {
return $this->_dependencyDB;
}
usort($packages, array(&$this, '_sortUninstall'));
}
function _sortUninstall($a, $b)
{
if (!$a->getDeps() && !$b->getDeps()) {
return 0; // neither package has dependencies, order is insignificant
}
if ($a->getDeps() && !$b->getDeps()) {
return -1; // $a must be installed after $b because $a has dependencies
}
if (!$a->getDeps() && $b->getDeps()) {
return 1; // $b must be installed after $a because $b has dependencies
}
// both packages have dependencies
if ($this->_dependencyDB->dependsOn($a, $b)) {
return -1;
}
if ($this->_dependencyDB->dependsOn($b, $a)) {
return 1;
}
return 0;
}
// }}}
// {{{ _sortDirs()
function _sortDirs($a, $b)
{
if (strnatcmp($a, $b) == -1) return 1;
if (strnatcmp($a, $b) == 1) return -1;
return 0;
}
// }}}
// {{{ _buildCallback()
function _buildCallback($what, $data)
{
if (($what == 'cmdoutput' && $this->debug > 1) ||
($what == 'output' && $this->debug > 0)) {
$this->ui->outputData(rtrim($data), 'build');
}
}
// }}}
}