9 * @author Stig Bakken <ssb@php.net>
10 * @author Tomas V.V. Cox <cox@idecnet.com>
11 * @author Martin Jansen <mj@php.net>
12 * @author Greg Beaver <cellog@php.net>
13 * @copyright 1997-2009 The Authors
14 * @license http://opensource.org/licenses/bsd-license.php New BSD License
15 * @version CVS: $Id: Installer.php 313024 2011-07-06 19:51:24Z dufuz $
16 * @link http://pear.php.net/package/PEAR
17 * @since File available since Release 0.1
21 * Used for installation groups in package.xml 2.0 and platform exceptions
23 require_once 'OS/Guess.php';
24 require_once 'PEAR/Downloader.php';
26 define('PEAR_INSTALLER_NOBINARY', -240);
28 * Administration class used to install PEAR packages and maintain the
29 * installed package database.
33 * @author Stig Bakken <ssb@php.net>
34 * @author Tomas V.V. Cox <cox@idecnet.com>
35 * @author Martin Jansen <mj@php.net>
36 * @author Greg Beaver <cellog@php.net>
37 * @copyright 1997-2009 The Authors
38 * @license http://opensource.org/licenses/bsd-license.php New BSD License
39 * @version Release: 1.9.4
40 * @link http://pear.php.net/package/PEAR
41 * @since Class available since Release 0.1
43 class PEAR_Installer extends PEAR_Downloader
47 /** name of the package directory, for example Foo-1.0
52 /** directory where PHP code files go
57 /** directory where PHP extension files go
62 /** directory where documentation goes
67 /** installation root directory (ala PHP's INSTALL_ROOT or
71 var $installroot = '';
78 /** temporary directory
84 * PEAR_Registry object used by the installer
90 * array of PEAR_Downloader_Packages
93 var $_downloadedPackages;
95 /** List of file transactions queued for an install/upgrade/uninstall.
99 * 0 => array("rename => array("from-file", "to-file")),
100 * 1 => array("delete" => array("file-to-delete")),
106 var $file_operations = array();
113 * PEAR_Installer constructor.
115 * @param object $ui user interface object (instance of PEAR_Frontend_*)
119 function PEAR_Installer(&$ui)
121 parent::PEAR_Common();
122 $this->setFrontendObject($ui);
123 $this->debug = $this->config->get('verbose');
126 function setOptions($options)
128 $this->_options = $options;
131 function setConfig(&$config)
133 $this->config = &$config;
134 $this->_registry = &$config->getRegistry();
139 function _removeBackups($files)
141 foreach ($files as $path) {
142 $this->addFileOperation('removebackup', array($path));
146 // {{{ _deletePackageFiles()
149 * Delete a package's installed files, does not remove empty directories.
151 * @param string package name
152 * @param string channel name
153 * @param bool if true, then files are backed up first
154 * @return bool TRUE on success, or a PEAR error on failure
157 function _deletePackageFiles($package, $channel = false, $backup = false)
160 $channel = 'pear.php.net';
163 if (!strlen($package)) {
164 return $this->raiseError("No package to uninstall given");
167 if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
168 // to avoid race conditions, include all possible needed files
169 require_once 'PEAR/Task/Common.php';
170 require_once 'PEAR/Task/Replace.php';
171 require_once 'PEAR/Task/Unixeol.php';
172 require_once 'PEAR/Task/Windowseol.php';
173 require_once 'PEAR/PackageFile/v1.php';
174 require_once 'PEAR/PackageFile/v2.php';
175 require_once 'PEAR/PackageFile/Generator/v1.php';
176 require_once 'PEAR/PackageFile/Generator/v2.php';
179 $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
180 if ($filelist == null) {
181 return $this->raiseError("$channel/$package not installed");
185 foreach ($filelist as $file => $props) {
186 if (empty($props['installed_as'])) {
190 $path = $props['installed_as'];
192 $this->addFileOperation('backup', array($path));
196 $this->addFileOperation('delete', array($path));
207 // {{{ _installFile()
210 * @param string filename
211 * @param array attributes from <file> tag in package.xml
212 * @param string path to install the file in
213 * @param array options from command-line
216 function _installFile($file, $atts, $tmp_path, $options)
218 // {{{ return if this file is meant for another platform
220 if (!isset($this->_registry)) {
221 $this->_registry = &$this->config->getRegistry();
224 if (isset($atts['platform'])) {
226 $os = new OS_Guess();
229 if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
231 $platform = substr($atts['platform'], 1);
234 $platform = $atts['platform'];
237 if ((bool) $os->matchSignature($platform) === $negate) {
238 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
239 return PEAR_INSTALLER_SKIPPED;
244 $channel = $this->pkginfo->getChannel();
245 // {{{ assemble the destination paths
246 switch ($atts['role']) {
249 $this->source_files++;
254 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
255 DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
256 unset($atts['baseinstalldir']);
260 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
263 $dest_dir = $this->config->get('bin_dir', null, $channel);
266 return $this->raiseError("Invalid role `$atts[role]' for file $file");
269 $save_destdir = $dest_dir;
270 if (!empty($atts['baseinstalldir'])) {
271 $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
274 if (dirname($file) != '.' && empty($atts['install-as'])) {
275 $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
278 if (empty($atts['install-as'])) {
279 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
281 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
283 $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
285 // Clean up the DIRECTORY_SEPARATOR mess
286 $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
287 list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
288 array(DIRECTORY_SEPARATOR,
290 DIRECTORY_SEPARATOR),
291 array($dest_file, $orig_file));
292 $final_dest_file = $installed_as = $dest_file;
293 if (isset($this->_options['packagingroot'])) {
294 $installedas_dest_dir = dirname($final_dest_file);
295 $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
296 $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
298 $installedas_dest_dir = dirname($final_dest_file);
299 $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
302 $dest_dir = dirname($final_dest_file);
303 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
304 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
305 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
309 if (empty($this->_options['register-only']) &&
310 (!file_exists($dest_dir) || !is_dir($dest_dir))) {
311 if (!$this->mkDirHier($dest_dir)) {
312 return $this->raiseError("failed to mkdir $dest_dir",
313 PEAR_INSTALLER_FAILED);
315 $this->log(3, "+ mkdir $dest_dir");
318 // pretty much nothing happens if we are only registering the install
319 if (empty($this->_options['register-only'])) {
320 if (empty($atts['replacements'])) {
321 if (!file_exists($orig_file)) {
322 return $this->raiseError("file $orig_file does not exist",
323 PEAR_INSTALLER_FAILED);
326 if (!@copy($orig_file, $dest_file)) {
327 return $this->raiseError("failed to write $dest_file: $php_errormsg",
328 PEAR_INSTALLER_FAILED);
331 $this->log(3, "+ cp $orig_file $dest_file");
332 if (isset($atts['md5sum'])) {
333 $md5sum = md5_file($dest_file);
336 // {{{ file with replacements
337 if (!file_exists($orig_file)) {
338 return $this->raiseError("file does not exist",
339 PEAR_INSTALLER_FAILED);
342 $contents = file_get_contents($orig_file);
343 if ($contents === false) {
347 if (isset($atts['md5sum'])) {
348 $md5sum = md5($contents);
351 $subst_from = $subst_to = array();
352 foreach ($atts['replacements'] as $a) {
354 if ($a['type'] == 'php-const') {
355 if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
356 eval("\$to = $a[to];");
358 if (!isset($options['soft'])) {
359 $this->log(0, "invalid php-const replacement: $a[to]");
363 } elseif ($a['type'] == 'pear-config') {
364 if ($a['to'] == 'master_server') {
365 $chan = $this->_registry->getChannel($channel);
366 if (!PEAR::isError($chan)) {
367 $to = $chan->getServer();
369 $to = $this->config->get($a['to'], null, $channel);
372 $to = $this->config->get($a['to'], null, $channel);
375 if (!isset($options['soft'])) {
376 $this->log(0, "invalid pear-config replacement: $a[to]");
380 } elseif ($a['type'] == 'package-info') {
381 if ($t = $this->pkginfo->packageInfo($a['to'])) {
384 if (!isset($options['soft'])) {
385 $this->log(0, "invalid package-info replacement: $a[to]");
391 $subst_from[] = $a['from'];
396 $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
397 if (sizeof($subst_from)) {
398 $contents = str_replace($subst_from, $subst_to, $contents);
401 $wp = @fopen($dest_file, "wb");
402 if (!is_resource($wp)) {
403 return $this->raiseError("failed to create $dest_file: $php_errormsg",
404 PEAR_INSTALLER_FAILED);
407 if (@fwrite($wp, $contents) === false) {
408 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
409 PEAR_INSTALLER_FAILED);
417 if (isset($md5sum)) {
418 if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
419 $this->log(2, "md5sum ok: $final_dest_file");
421 if (empty($options['force'])) {
423 if (file_exists($dest_file)) {
427 if (!isset($options['ignore-errors'])) {
428 return $this->raiseError("bad md5sum for file $final_dest_file",
429 PEAR_INSTALLER_FAILED);
432 if (!isset($options['soft'])) {
433 $this->log(0, "warning : bad md5sum for file $final_dest_file");
436 if (!isset($options['soft'])) {
437 $this->log(0, "warning : bad md5sum for file $final_dest_file");
443 // {{{ set file permissions
445 if ($atts['role'] == 'script') {
446 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
447 $this->log(3, "+ chmod +x $dest_file");
449 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
452 if ($atts['role'] != 'src') {
453 $this->addFileOperation("chmod", array($mode, $dest_file));
454 if (!@chmod($dest_file, $mode)) {
455 if (!isset($options['soft'])) {
456 $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
463 if ($atts['role'] == 'src') {
464 rename($dest_file, $final_dest_file);
465 $this->log(2, "renamed source file $dest_file to $final_dest_file");
467 $this->addFileOperation("rename", array($dest_file, $final_dest_file,
468 $atts['role'] == 'ext'));
472 // Store the full path where the file was installed for easy unistall
473 if ($atts['role'] != 'script') {
474 $loc = $this->config->get($atts['role'] . '_dir');
476 $loc = $this->config->get('bin_dir');
479 if ($atts['role'] != 'src') {
480 $this->addFileOperation("installed_as", array($file, $installed_as,
482 dirname(substr($installedas_dest_file, strlen($loc)))));
485 //$this->log(2, "installed: $dest_file");
486 return PEAR_INSTALLER_OK;
490 // {{{ _installFile2()
493 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
494 * @param string filename
495 * @param array attributes from <file> tag in package.xml
496 * @param string path to install the file in
497 * @param array options from command-line
500 function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
503 if (!isset($this->_registry)) {
504 $this->_registry = &$this->config->getRegistry();
507 $channel = $pkg->getChannel();
508 // {{{ assemble the destination paths
509 if (!in_array($atts['attribs']['role'],
510 PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
511 return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
515 $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
516 $err = $role->setup($this, $pkg, $atts['attribs'], $file);
517 if (PEAR::isError($err)) {
521 if (!$role->isInstallable()) {
525 $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
526 if (PEAR::isError($info)) {
530 list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
531 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
532 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
535 $final_dest_file = $installed_as = $dest_file;
536 if (isset($this->_options['packagingroot'])) {
537 $final_dest_file = $this->_prependPath($final_dest_file,
538 $this->_options['packagingroot']);
541 $dest_dir = dirname($final_dest_file);
542 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
545 if (empty($this->_options['register-only'])) {
546 if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
547 if (!$this->mkDirHier($dest_dir)) {
548 return $this->raiseError("failed to mkdir $dest_dir",
549 PEAR_INSTALLER_FAILED);
551 $this->log(3, "+ mkdir $dest_dir");
555 $attribs = $atts['attribs'];
556 unset($atts['attribs']);
557 // pretty much nothing happens if we are only registering the install
558 if (empty($this->_options['register-only'])) {
559 if (!count($atts)) { // no tasks
560 if (!file_exists($orig_file)) {
561 return $this->raiseError("file $orig_file does not exist",
562 PEAR_INSTALLER_FAILED);
565 if (!@copy($orig_file, $dest_file)) {
566 return $this->raiseError("failed to write $dest_file: $php_errormsg",
567 PEAR_INSTALLER_FAILED);
570 $this->log(3, "+ cp $orig_file $dest_file");
571 if (isset($attribs['md5sum'])) {
572 $md5sum = md5_file($dest_file);
574 } else { // file with tasks
575 if (!file_exists($orig_file)) {
576 return $this->raiseError("file $orig_file does not exist",
577 PEAR_INSTALLER_FAILED);
580 $contents = file_get_contents($orig_file);
581 if ($contents === false) {
585 if (isset($attribs['md5sum'])) {
586 $md5sum = md5($contents);
589 foreach ($atts as $tag => $raw) {
590 $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
591 $task = "PEAR_Task_$tag";
592 $task = &new $task($this->config, $this, PEAR_TASK_INSTALL);
593 if (!$task->isScript()) { // scripts are only handled after installation
594 $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
595 $res = $task->startSession($pkg, $contents, $final_dest_file);
596 if ($res === false) {
597 continue; // skip this file
600 if (PEAR::isError($res)) {
604 $contents = $res; // save changes
607 $wp = @fopen($dest_file, "wb");
608 if (!is_resource($wp)) {
609 return $this->raiseError("failed to create $dest_file: $php_errormsg",
610 PEAR_INSTALLER_FAILED);
613 if (fwrite($wp, $contents) === false) {
614 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
615 PEAR_INSTALLER_FAILED);
623 if (isset($md5sum)) {
624 // Make sure the original md5 sum matches with expected
625 if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
626 $this->log(2, "md5sum ok: $final_dest_file");
628 if (isset($contents)) {
629 // set md5 sum based on $content in case any tasks were run.
630 $real_atts['attribs']['md5sum'] = md5($contents);
633 if (empty($options['force'])) {
635 if (file_exists($dest_file)) {
639 if (!isset($options['ignore-errors'])) {
640 return $this->raiseError("bad md5sum for file $final_dest_file",
641 PEAR_INSTALLER_FAILED);
644 if (!isset($options['soft'])) {
645 $this->log(0, "warning : bad md5sum for file $final_dest_file");
648 if (!isset($options['soft'])) {
649 $this->log(0, "warning : bad md5sum for file $final_dest_file");
654 $real_atts['attribs']['md5sum'] = md5_file($dest_file);
658 // {{{ set file permissions
660 if ($role->isExecutable()) {
661 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
662 $this->log(3, "+ chmod +x $dest_file");
664 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
667 if ($attribs['role'] != 'src') {
668 $this->addFileOperation("chmod", array($mode, $dest_file));
669 if (!@chmod($dest_file, $mode)) {
670 if (!isset($options['soft'])) {
671 $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
678 if ($attribs['role'] == 'src') {
679 rename($dest_file, $final_dest_file);
680 $this->log(2, "renamed source file $dest_file to $final_dest_file");
682 $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
686 // Store the full path where the file was installed for easy uninstall
687 if ($attribs['role'] != 'src') {
688 $loc = $this->config->get($role->getLocationConfig(), null, $channel);
689 $this->addFileOperation('installed_as', array($file, $installed_as,
691 dirname(substr($installed_as, strlen($loc)))));
694 //$this->log(2, "installed: $dest_file");
695 return PEAR_INSTALLER_OK;
699 // {{{ addFileOperation()
702 * Add a file operation to the current file transaction.
704 * @see startFileTransaction()
705 * @param string $type This can be one of:
706 * - rename: rename a file ($data has 3 values)
707 * - backup: backup an existing file ($data has 1 value)
708 * - removebackup: clean up backups created during install ($data has 1 value)
709 * - chmod: change permissions on a file ($data has 2 values)
710 * - delete: delete a file ($data has 1 value)
711 * - rmdir: delete a directory if empty ($data has 1 value)
712 * - installed_as: mark a file as installed ($data has 4 values).
713 * @param array $data For all file operations, this array must contain the
714 * full path to the file or directory that is being operated on. For
715 * the rename command, the first parameter must be the file to rename,
716 * the second its new name, the third whether this is a PHP extension.
718 * The installed_as operation contains 4 elements in this order:
719 * 1. Filename as listed in the filelist element from package.xml
720 * 2. Full path to the installed file
721 * 3. Full path from the php_dir configuration variable used in this
723 * 4. Relative path from the php_dir that this file is installed in
725 function addFileOperation($type, $data)
727 if (!is_array($data)) {
728 return $this->raiseError('Internal Error: $data in addFileOperation'
729 . ' must be an array, was ' . gettype($data));
732 if ($type == 'chmod') {
733 $octmode = decoct($data[0]);
734 $this->log(3, "adding to transaction: $type $octmode $data[1]");
736 $this->log(3, "adding to transaction: $type " . implode(" ", $data));
738 $this->file_operations[] = array($type, $data);
742 // {{{ startFileTransaction()
744 function startFileTransaction($rollback_in_case = false)
746 if (count($this->file_operations) && $rollback_in_case) {
747 $this->rollbackFileTransaction();
749 $this->file_operations = array();
753 // {{{ commitFileTransaction()
755 function commitFileTransaction()
757 // {{{ first, check permissions and such manually
759 foreach ($this->file_operations as $key => $tr) {
760 list($type, $data) = $tr;
763 if (!file_exists($data[0])) {
764 $errors[] = "cannot rename file $data[0], doesn't exist";
767 // check that dest dir. is writable
768 if (!is_writable(dirname($data[1]))) {
769 $errors[] = "permission denied ($type): $data[1]";
773 // check that file is writable
774 if (!is_writable($data[1])) {
775 $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
779 if (!file_exists($data[0])) {
780 $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
782 // check that directory is writable
783 if (file_exists($data[0])) {
784 if (!is_writable(dirname($data[0]))) {
785 $errors[] = "permission denied ($type): $data[0]";
787 // make sure the file to be deleted can be opened for writing
789 if (!is_dir($data[0]) &&
790 (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
791 $errors[] = "permission denied ($type): $data[0]";
797 /* Verify we are not deleting a file owned by another package
798 * This can happen when a file moves from package A to B in
799 * an upgrade ala http://pear.php.net/17986
802 'package' => strtolower($this->pkginfo->getName()),
803 'channel' => strtolower($this->pkginfo->getChannel()),
805 $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
806 if (is_array($result)) {
807 $res = array_diff($result, $info);
809 $new = $this->_registry->getPackage($result[1], $result[0]);
810 $this->file_operations[$key] = false;
811 $this->log(3, "file $data[0] was scheduled for removal from {$this->pkginfo->getName()} but is owned by {$new->getChannel()}/{$new->getName()}, removal has been cancelled.");
821 $n = count($this->file_operations);
822 $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
826 foreach ($errors as $error) {
827 if (!isset($this->_options['soft'])) {
828 $this->log(1, $error);
832 if (!isset($this->_options['ignore-errors'])) {
837 $this->_dirtree = array();
838 // {{{ really commit the transaction
839 foreach ($this->file_operations as $i => $tr) {
841 // support removal of non-existing backups
845 list($type, $data) = $tr;
848 if (!file_exists($data[0])) {
849 $this->file_operations[$i] = false;
853 if (!@copy($data[0], $data[0] . '.bak')) {
854 $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
855 '.bak ' . $php_errormsg);
858 $this->log(3, "+ backup $data[0] to $data[0].bak");
861 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
862 unlink($data[0] . '.bak');
863 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
867 $test = file_exists($data[1]) ? @unlink($data[1]) : null;
868 if (!$test && file_exists($data[1])) {
870 $extra = ', this extension must be installed manually. Rename to "' .
871 basename($data[1]) . '"';
876 if (!isset($this->_options['soft'])) {
877 $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
881 if (!isset($this->_options['ignore-errors'])) {
886 // permissions issues with rename - copy() is far superior
887 $perms = @fileperms($data[0]);
888 if (!@copy($data[0], $data[1])) {
889 $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
890 ' ' . $php_errormsg);
894 // copy over permissions, otherwise they are lost
895 @chmod($data[1], $perms);
897 $this->log(3, "+ mv $data[0] $data[1]");
900 if (!@chmod($data[1], $data[0])) {
901 $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
902 decoct($data[0]) . ' ' . $php_errormsg);
906 $octmode = decoct($data[0]);
907 $this->log(3, "+ chmod $octmode $data[1]");
910 if (file_exists($data[0])) {
911 if (!@unlink($data[0])) {
912 $this->log(1, 'Could not delete ' . $data[0] . ' ' .
916 $this->log(3, "+ rm $data[0]");
920 if (file_exists($data[0])) {
922 $testme = opendir($data[0]);
923 while (false !== ($entry = readdir($testme))) {
924 if ($entry == '.' || $entry == '..') {
928 break 2; // this directory is not empty and can't be
933 if (!@rmdir($data[0])) {
934 $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
938 $this->log(3, "+ rmdir $data[0]");
943 $this->pkginfo->setInstalledAs($data[0], $data[1]);
944 if (!isset($this->_dirtree[dirname($data[1])])) {
945 $this->_dirtree[dirname($data[1])] = true;
946 $this->pkginfo->setDirtree(dirname($data[1]));
948 while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
949 $data[3] != '/' && $data[3] != '\\') {
950 $this->pkginfo->setDirtree($pp =
951 $this->_prependPath($data[3], $data[2]));
952 $this->_dirtree[$pp] = true;
953 $data[3] = dirname($data[3]);
960 $this->log(2, "successfully committed $n file operations");
961 $this->file_operations = array();
966 // {{{ rollbackFileTransaction()
968 function rollbackFileTransaction()
970 $n = count($this->file_operations);
971 $this->log(2, "rolling back $n file operations");
972 foreach ($this->file_operations as $tr) {
973 list($type, $data) = $tr;
976 if (file_exists($data[0] . '.bak')) {
977 if (file_exists($data[0] && is_writable($data[0]))) {
980 @copy($data[0] . '.bak', $data[0]);
981 $this->log(3, "+ restore $data[0] from $data[0].bak");
985 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
986 unlink($data[0] . '.bak');
987 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
992 $this->log(3, "+ rm $data[0]");
996 $this->log(3, "+ rmdir $data[0]");
1002 case 'installed_as':
1003 $this->pkginfo->setInstalledAs($data[0], false);
1007 $this->pkginfo->resetDirtree();
1008 $this->file_operations = array();
1012 // {{{ mkDirHier($dir)
1014 function mkDirHier($dir)
1016 $this->addFileOperation('mkdir', array($dir));
1017 return parent::mkDirHier($dir);
1024 * Download any files and their dependencies, if necessary
1026 * @param array a mixed list of package names, local files, or package.xml
1027 * @param PEAR_Config
1028 * @param array options from the command line
1029 * @param array this is the array that will be populated with packages to
1030 * install. Format of each entry:
1033 * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
1034 * 'info' => array() // parsed package.xml
1037 * @param array this will be populated with any error messages
1038 * @param false private recursion variable
1039 * @param false private recursion variable
1040 * @param false private recursion variable
1041 * @deprecated in favor of PEAR_Downloader
1043 function download($packages, $options, &$config, &$installpackages,
1044 &$errors, $installed = false, $willinstall = false, $state = false)
1046 // trickiness: initialize here
1047 parent::PEAR_Downloader($this->ui, $options, $config);
1048 $ret = parent::download($packages);
1049 $errors = $this->getErrorMsgs();
1050 $installpackages = $this->getDownloadedPackages();
1051 trigger_error("PEAR Warning: PEAR_Installer::download() is deprecated " .
1052 "in favor of PEAR_Downloader class", E_USER_WARNING);
1057 // {{{ _parsePackageXml()
1059 function _parsePackageXml(&$descfile)
1061 // Parse xml file -----------------------------------------------
1062 $pkg = new PEAR_PackageFile($this->config, $this->debug);
1063 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1064 $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
1065 PEAR::staticPopErrorHandling();
1066 if (PEAR::isError($p)) {
1067 if (is_array($p->getUserInfo())) {
1068 foreach ($p->getUserInfo() as $err) {
1069 $loglevel = $err['level'] == 'error' ? 0 : 1;
1070 if (!isset($this->_options['soft'])) {
1071 $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
1075 return $this->raiseError('Installation failed: invalid package file');
1078 $descfile = $p->getPackageFile();
1084 * Set the list of PEAR_Downloader_Package objects to allow more sane
1085 * dependency validation
1088 function setDownloadedPackages(&$pkgs)
1090 PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1091 $err = $this->analyzeDependencies($pkgs);
1092 PEAR::popErrorHandling();
1093 if (PEAR::isError($err)) {
1096 $this->_downloadedPackages = &$pkgs;
1100 * Set the list of PEAR_Downloader_Package objects to allow more sane
1101 * dependency validation
1104 function setUninstallPackages(&$pkgs)
1106 $this->_downloadedPackages = &$pkgs;
1109 function getInstallPackages()
1111 return $this->_downloadedPackages;
1117 * Installs the files within the package file specified.
1119 * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
1120 * or a pre-initialized packagefile object
1121 * @param array $options
1122 * recognized options:
1123 * - installroot : optional prefix directory for installation
1124 * - force : force installation
1125 * - register-only : update registry but don't install files
1126 * - upgrade : upgrade existing install
1127 * - soft : fail silently
1128 * - nodeps : ignore dependency conflicts/missing dependencies
1129 * - alldeps : install all dependencies
1130 * - onlyreqdeps : install only required dependencies
1132 * @return array|PEAR_Error package info if successful
1134 function install($pkgfile, $options = array())
1136 $this->_options = $options;
1137 $this->_registry = &$this->config->getRegistry();
1138 if (is_object($pkgfile)) {
1140 $pkg = $pkgfile->getPackageFile();
1141 $pkgfile = $pkg->getArchiveFile();
1142 $descfile = $pkg->getPackageFile();
1144 $descfile = $pkgfile;
1145 $pkg = $this->_parsePackageXml($descfile);
1146 if (PEAR::isError($pkg)) {
1151 $tmpdir = dirname($descfile);
1152 if (realpath($descfile) != realpath($pkgfile)) {
1153 // Use the temp_dir since $descfile can contain the download dir path
1154 $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
1155 $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
1157 $tar = new Archive_Tar($pkgfile);
1158 if (!$tar->extract($tmpdir)) {
1159 return $this->raiseError("unable to unpack $pkgfile");
1163 $pkgname = $pkg->getName();
1164 $channel = $pkg->getChannel();
1165 if (isset($this->_options['packagingroot'])) {
1166 $regdir = $this->_prependPath(
1167 $this->config->get('php_dir', null, 'pear.php.net'),
1168 $this->_options['packagingroot']);
1170 $packrootphp_dir = $this->_prependPath(
1171 $this->config->get('php_dir', null, $channel),
1172 $this->_options['packagingroot']);
1175 if (isset($options['installroot'])) {
1176 $this->config->setInstallRoot($options['installroot']);
1177 $this->_registry = &$this->config->getRegistry();
1178 $installregistry = &$this->_registry;
1179 $this->installroot = ''; // all done automagically now
1180 $php_dir = $this->config->get('php_dir', null, $channel);
1182 $this->config->setInstallRoot(false);
1183 $this->_registry = &$this->config->getRegistry();
1184 if (isset($this->_options['packagingroot'])) {
1185 $installregistry = &new PEAR_Registry($regdir);
1186 if (!$installregistry->channelExists($channel, true)) {
1187 // we need to fake a channel-discover of this channel
1188 $chanobj = $this->_registry->getChannel($channel, true);
1189 $installregistry->addChannel($chanobj);
1191 $php_dir = $packrootphp_dir;
1193 $installregistry = &$this->_registry;
1194 $php_dir = $this->config->get('php_dir', null, $channel);
1196 $this->installroot = '';
1199 // {{{ checks to do when not in "force" mode
1200 if (empty($options['force']) &&
1201 (file_exists($this->config->get('php_dir')) &&
1202 is_dir($this->config->get('php_dir')))) {
1203 $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
1204 $instfilelist = $pkg->getInstallationFileList(true);
1205 if (PEAR::isError($instfilelist)) {
1206 return $instfilelist;
1209 // ensure we have the most accurate registry
1210 $installregistry->flushFileMap();
1211 $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
1212 if (PEAR::isError($test)) {
1216 if (sizeof($test)) {
1217 $pkgs = $this->getInstallPackages();
1219 foreach ($pkgs as $param) {
1220 if ($pkg->isSubpackageOf($param)) {
1227 // subpackages can conflict with earlier versions of parent packages
1228 $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
1230 foreach ($tmp as $file => $info) {
1231 if (is_array($info)) {
1232 if (strtolower($info[1]) == strtolower($param->getPackage()) &&
1233 strtolower($info[0]) == strtolower($param->getChannel())
1235 if (isset($parentreg['filelist'][$file])) {
1236 unset($parentreg['filelist'][$file]);
1238 $pos = strpos($file, '/');
1239 $basedir = substr($file, 0, $pos);
1240 $file2 = substr($file, $pos + 1);
1241 if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1242 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1244 unset($parentreg['filelist'][$file2]);
1248 unset($test[$file]);
1251 if (strtolower($param->getChannel()) != 'pear.php.net') {
1255 if (strtolower($info) == strtolower($param->getPackage())) {
1256 if (isset($parentreg['filelist'][$file])) {
1257 unset($parentreg['filelist'][$file]);
1259 $pos = strpos($file, '/');
1260 $basedir = substr($file, 0, $pos);
1261 $file2 = substr($file, $pos + 1);
1262 if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1263 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1265 unset($parentreg['filelist'][$file2]);
1269 unset($test[$file]);
1274 $pfk = &new PEAR_PackageFile($this->config);
1275 $parentpkg = &$pfk->fromArray($parentreg);
1276 $installregistry->updatePackage2($parentpkg);
1279 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
1281 foreach ($tmp as $file => $info) {
1282 if (is_string($info)) {
1283 // pear.php.net packages are always stored as strings
1284 if (strtolower($info) == strtolower($param->getPackage())) {
1285 // upgrading existing package
1286 unset($test[$file]);
1293 $msg = "$channel/$pkgname: conflicting files found:\n";
1294 $longest = max(array_map("strlen", array_keys($test)));
1295 $fmt = "%${longest}s (%s)\n";
1296 foreach ($test as $file => $info) {
1297 if (!is_array($info)) {
1298 $info = array('pear.php.net', $info);
1300 $info = $info[0] . '/' . $info[1];
1301 $msg .= sprintf($fmt, $file, $info);
1304 if (!isset($options['ignore-errors'])) {
1305 return $this->raiseError($msg);
1308 if (!isset($options['soft'])) {
1309 $this->log(0, "WARNING: $msg");
1316 $this->startFileTransaction();
1318 $usechannel = $channel;
1319 if ($channel == 'pecl.php.net') {
1320 $test = $installregistry->packageExists($pkgname, $channel);
1322 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1323 $usechannel = 'pear.php.net';
1326 $test = $installregistry->packageExists($pkgname, $channel);
1329 if (empty($options['upgrade']) && empty($options['soft'])) {
1330 // checks to do only when installing new packages
1331 if (empty($options['force']) && $test) {
1332 return $this->raiseError("$channel/$pkgname is already installed");
1337 $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1338 $v2 = $pkg->getVersion();
1339 $cmp = version_compare("$v1", "$v2", 'gt');
1340 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
1341 return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
1346 // Do cleanups for upgrade and install, remove old release's files first
1347 if ($test && empty($options['register-only'])) {
1348 // when upgrading, remove old release's files first:
1349 if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
1351 if (!isset($options['ignore-errors'])) {
1352 return $this->raiseError($err);
1355 if (!isset($options['soft'])) {
1356 $this->log(0, 'WARNING: ' . $err->getMessage());
1363 // {{{ Copy files to dest dir ---------------------------------------
1365 // info from the package it self we want to access from _installFile
1366 $this->pkginfo = &$pkg;
1367 // used to determine whether we should build any C code
1368 $this->source_files = 0;
1370 $savechannel = $this->config->get('default_channel');
1371 if (empty($options['register-only']) && !is_dir($php_dir)) {
1372 if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
1373 return $this->raiseError("no installation destination directory '$php_dir'\n");
1377 if (substr($pkgfile, -4) != '.xml') {
1378 $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
1381 $this->configSet('default_channel', $channel);
1382 // {{{ install files
1384 $ver = $pkg->getPackagexmlVersion();
1385 if (version_compare($ver, '2.0', '>=')) {
1386 $filelist = $pkg->getInstallationFilelist();
1388 $filelist = $pkg->getFileList();
1391 if (PEAR::isError($filelist)) {
1395 $p = &$installregistry->getPackage($pkgname, $channel);
1396 $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
1398 $pkg->resetFilelist();
1399 $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
1400 'version', $pkg->getChannel()));
1401 foreach ($filelist as $file => $atts) {
1402 $this->expectError(PEAR_INSTALLER_FAILED);
1403 if ($pkg->getPackagexmlVersion() == '1.0') {
1404 $res = $this->_installFile($file, $atts, $tmpdir, $options);
1406 $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
1410 if (PEAR::isError($res)) {
1411 if (empty($options['ignore-errors'])) {
1412 $this->rollbackFileTransaction();
1413 if ($res->getMessage() == "file does not exist") {
1414 $this->raiseError("file $file in package.xml does not exist");
1417 return $this->raiseError($res);
1420 if (!isset($options['soft'])) {
1421 $this->log(0, "Warning: " . $res->getMessage());
1425 $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
1426 if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
1427 // Register files that were installed
1428 $pkg->installedFile($file, $atts);
1433 // {{{ compile and install source files
1434 if ($this->source_files > 0 && empty($options['nobuild'])) {
1435 if (PEAR::isError($err =
1436 $this->_compileSourceFiles($savechannel, $pkg))) {
1442 if (isset($backedup)) {
1443 $this->_removeBackups($backedup);
1446 if (!$this->commitFileTransaction()) {
1447 $this->rollbackFileTransaction();
1448 $this->configSet('default_channel', $savechannel);
1449 return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
1454 $installphase = 'install';
1455 $oldversion = false;
1456 // {{{ Register that the package is installed -----------------------
1457 if (empty($options['upgrade'])) {
1458 // if 'force' is used, replace the info in registry
1459 $usechannel = $channel;
1460 if ($channel == 'pecl.php.net') {
1461 $test = $installregistry->packageExists($pkgname, $channel);
1463 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1464 $usechannel = 'pear.php.net';
1467 $test = $installregistry->packageExists($pkgname, $channel);
1470 if (!empty($options['force']) && $test) {
1471 $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1472 $installregistry->deletePackage($pkgname, $usechannel);
1474 $ret = $installregistry->addPackage2($pkg);
1477 $this->startFileTransaction();
1478 // attempt to delete empty directories
1479 uksort($dirtree, array($this, '_sortDirs'));
1480 foreach($dirtree as $dir => $notused) {
1481 $this->addFileOperation('rmdir', array($dir));
1483 $this->commitFileTransaction();
1486 $usechannel = $channel;
1487 if ($channel == 'pecl.php.net') {
1488 $test = $installregistry->packageExists($pkgname, $channel);
1490 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1491 $usechannel = 'pear.php.net';
1494 $test = $installregistry->packageExists($pkgname, $channel);
1497 // new: upgrade installs a package if it isn't installed
1499 $ret = $installregistry->addPackage2($pkg);
1501 if ($usechannel != $channel) {
1502 $installregistry->deletePackage($pkgname, $usechannel);
1503 $ret = $installregistry->addPackage2($pkg);
1505 $ret = $installregistry->updatePackage2($pkg);
1507 $installphase = 'upgrade';
1512 $this->configSet('default_channel', $savechannel);
1513 return $this->raiseError("Adding package $channel/$pkgname to registry failed");
1517 $this->configSet('default_channel', $savechannel);
1518 if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
1519 if (PEAR_Task_Common::hasPostinstallTasks()) {
1520 PEAR_Task_Common::runPostinstallTasks($installphase);
1524 return $pkg->toArray(true);
1529 // {{{ _compileSourceFiles()
1532 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1534 function _compileSourceFiles($savechannel, &$filelist)
1536 require_once 'PEAR/Builder.php';
1537 $this->log(1, "$this->source_files source files, building");
1538 $bob = &new PEAR_Builder($this->ui);
1539 $bob->debug = $this->debug;
1540 $built = $bob->build($filelist, array(&$this, '_buildCallback'));
1541 if (PEAR::isError($built)) {
1542 $this->rollbackFileTransaction();
1543 $this->configSet('default_channel', $savechannel);
1547 $this->log(1, "\nBuild process completed successfully");
1548 foreach ($built as $ext) {
1549 $bn = basename($ext['file']);
1550 list($_ext_name, $_ext_suff) = explode('.', $bn);
1551 if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
1552 if (extension_loaded($_ext_name)) {
1553 $this->raiseError("Extension '$_ext_name' already loaded. " .
1554 'Please unload it in your php.ini file ' .
1555 'prior to install or upgrade');
1562 $dest = $ext['dest'];
1563 $packagingroot = '';
1564 if (isset($this->_options['packagingroot'])) {
1565 $packagingroot = $this->_options['packagingroot'];
1568 $copyto = $this->_prependPath($dest, $packagingroot);
1569 $extra = $copyto != $dest ? " as '$copyto'" : '';
1570 $this->log(1, "Installing '$dest'$extra");
1572 $copydir = dirname($copyto);
1573 // pretty much nothing happens if we are only registering the install
1574 if (empty($this->_options['register-only'])) {
1575 if (!file_exists($copydir) || !is_dir($copydir)) {
1576 if (!$this->mkDirHier($copydir)) {
1577 return $this->raiseError("failed to mkdir $copydir",
1578 PEAR_INSTALLER_FAILED);
1581 $this->log(3, "+ mkdir $copydir");
1584 if (!@copy($ext['file'], $copyto)) {
1585 return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
1588 $this->log(3, "+ cp $ext[file] $copyto");
1589 $this->addFileOperation('rename', array($ext['file'], $copyto));
1591 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
1592 $this->addFileOperation('chmod', array($mode, $copyto));
1593 if (!@chmod($copyto, $mode)) {
1594 $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
1603 'installed_as' => $dest,
1604 'php_api' => $ext['php_api'],
1605 'zend_mod_api' => $ext['zend_mod_api'],
1606 'zend_ext_api' => $ext['zend_ext_api'],
1609 if ($filelist->getPackageXmlVersion() == '1.0') {
1610 $filelist->installedFile($bn, $data);
1612 $filelist->installedFile($bn, array('attribs' => $data));
1618 function &getUninstallPackages()
1620 return $this->_downloadedPackages;
1625 * Uninstall a package
1627 * This method removes all files installed by the application, and then
1628 * removes any empty directories.
1629 * @param string package name
1630 * @param array Command-line options. Possibilities include:
1632 * - installroot: base installation dir, if not the default
1633 * - register-only : update registry but don't remove files
1634 * - nodeps: do not process dependencies of other packages to ensure
1635 * uninstallation does not break things
1637 function uninstall($package, $options = array())
1639 $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
1640 $this->config->setInstallRoot($installRoot);
1642 $this->installroot = '';
1643 $this->_registry = &$this->config->getRegistry();
1644 if (is_object($package)) {
1645 $channel = $package->getChannel();
1647 $package = $pkg->getPackage();
1650 $info = $this->_registry->parsePackageName($package,
1651 $this->config->get('default_channel'));
1652 $channel = $info['channel'];
1653 $package = $info['package'];
1656 $savechannel = $this->config->get('default_channel');
1657 $this->configSet('default_channel', $channel);
1658 if (!is_object($pkg)) {
1659 $pkg = $this->_registry->getPackage($package, $channel);
1663 $this->configSet('default_channel', $savechannel);
1664 return $this->raiseError($this->_registry->parsedPackageNameToString(
1666 'channel' => $channel,
1667 'package' => $package
1668 ), true) . ' not installed');
1671 if ($pkg->getInstalledBinary()) {
1672 // this is just an alias for a binary package
1673 return $this->_registry->deletePackage($package, $channel);
1676 $filelist = $pkg->getFilelist();
1677 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1678 if (!class_exists('PEAR_Dependency2')) {
1679 require_once 'PEAR/Dependency2.php';
1682 $depchecker = &new PEAR_Dependency2($this->config, $options,
1683 array('channel' => $channel, 'package' => $package),
1684 PEAR_VALIDATE_UNINSTALLING);
1685 $e = $depchecker->validatePackageUninstall($this);
1686 PEAR::staticPopErrorHandling();
1687 if (PEAR::isError($e)) {
1688 if (!isset($options['ignore-errors'])) {
1689 return $this->raiseError($e);
1692 if (!isset($options['soft'])) {
1693 $this->log(0, 'WARNING: ' . $e->getMessage());
1695 } elseif (is_array($e)) {
1696 if (!isset($options['soft'])) {
1697 $this->log(0, $e[0]);
1701 $this->pkginfo = &$pkg;
1702 // pretty much nothing happens if we are only registering the uninstall
1703 if (empty($options['register-only'])) {
1704 // {{{ Delete the files
1705 $this->startFileTransaction();
1706 PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1707 if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
1708 PEAR::popErrorHandling();
1709 $this->rollbackFileTransaction();
1710 $this->configSet('default_channel', $savechannel);
1711 if (!isset($options['ignore-errors'])) {
1712 return $this->raiseError($err);
1715 if (!isset($options['soft'])) {
1716 $this->log(0, 'WARNING: ' . $err->getMessage());
1719 PEAR::popErrorHandling();
1722 if (!$this->commitFileTransaction()) {
1723 $this->rollbackFileTransaction();
1724 if (!isset($options['ignore-errors'])) {
1725 return $this->raiseError("uninstall failed");
1728 if (!isset($options['soft'])) {
1729 $this->log(0, 'WARNING: uninstall failed');
1732 $this->startFileTransaction();
1733 $dirtree = $pkg->getDirTree();
1734 if ($dirtree === false) {
1735 $this->configSet('default_channel', $savechannel);
1736 return $this->_registry->deletePackage($package, $channel);
1739 // attempt to delete empty directories
1740 uksort($dirtree, array($this, '_sortDirs'));
1741 foreach($dirtree as $dir => $notused) {
1742 $this->addFileOperation('rmdir', array($dir));
1745 if (!$this->commitFileTransaction()) {
1746 $this->rollbackFileTransaction();
1747 if (!isset($options['ignore-errors'])) {
1748 return $this->raiseError("uninstall failed");
1751 if (!isset($options['soft'])) {
1752 $this->log(0, 'WARNING: uninstall failed');
1759 $this->configSet('default_channel', $savechannel);
1760 // Register that the package is no longer installed
1761 return $this->_registry->deletePackage($package, $channel);
1765 * Sort a list of arrays of array(downloaded packagefilename) by dependency.
1767 * It also removes duplicate dependencies
1768 * @param array an array of PEAR_PackageFile_v[1/2] objects
1769 * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
1771 function sortPackagesForUninstall(&$packages)
1773 $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
1774 if (PEAR::isError($this->_dependencyDB)) {
1775 return $this->_dependencyDB;
1777 usort($packages, array(&$this, '_sortUninstall'));
1780 function _sortUninstall($a, $b)
1782 if (!$a->getDeps() && !$b->getDeps()) {
1783 return 0; // neither package has dependencies, order is insignificant
1785 if ($a->getDeps() && !$b->getDeps()) {
1786 return -1; // $a must be installed after $b because $a has dependencies
1788 if (!$a->getDeps() && $b->getDeps()) {
1789 return 1; // $b must be installed after $a because $b has dependencies
1791 // both packages have dependencies
1792 if ($this->_dependencyDB->dependsOn($a, $b)) {
1795 if ($this->_dependencyDB->dependsOn($b, $a)) {
1803 function _sortDirs($a, $b)
1805 if (strnatcmp($a, $b) == -1) return 1;
1806 if (strnatcmp($a, $b) == 1) return -1;
1812 // {{{ _buildCallback()
1814 function _buildCallback($what, $data)
1816 if (($what == 'cmdoutput' && $this->debug > 1) ||
1817 ($what == 'output' && $this->debug > 0)) {
1818 $this->ui->outputData(rtrim($data), 'build');