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 * @link http://pear.php.net/package/PEAR
16 * @since File available since Release 0.1
20 * Used for installation groups in package.xml 2.0 and platform exceptions
22 require_once 'OS/Guess.php';
23 require_once 'PEAR/Downloader.php';
25 define('PEAR_INSTALLER_NOBINARY', -240);
27 * Administration class used to install PEAR packages and maintain the
28 * installed package database.
32 * @author Stig Bakken <ssb@php.net>
33 * @author Tomas V.V. Cox <cox@idecnet.com>
34 * @author Martin Jansen <mj@php.net>
35 * @author Greg Beaver <cellog@php.net>
36 * @copyright 1997-2009 The Authors
37 * @license http://opensource.org/licenses/bsd-license.php New BSD License
38 * @version Release: 1.10.1
39 * @link http://pear.php.net/package/PEAR
40 * @since Class available since Release 0.1
42 class PEAR_Installer extends PEAR_Downloader
46 /** name of the package directory, for example Foo-1.0
51 /** directory where PHP code files go
56 /** directory where PHP extension files go
61 /** directory where documentation goes
66 /** installation root directory (ala PHP's INSTALL_ROOT or
70 var $installroot = '';
77 /** temporary directory
83 * PEAR_Registry object used by the installer
89 * array of PEAR_Downloader_Packages
92 var $_downloadedPackages;
94 /** List of file transactions queued for an install/upgrade/uninstall.
98 * 0 => array("rename => array("from-file", "to-file")),
99 * 1 => array("delete" => array("file-to-delete")),
105 var $file_operations = array();
112 * PEAR_Installer constructor.
114 * @param object $ui user interface object (instance of PEAR_Frontend_*)
118 function __construct(&$ui)
120 parent::__construct($ui, array(), null);
121 $this->setFrontendObject($ui);
122 $this->debug = $this->config->get('verbose');
125 function setOptions($options)
127 $this->_options = $options;
130 function setConfig(&$config)
132 $this->config = &$config;
133 $this->_registry = &$config->getRegistry();
138 function _removeBackups($files)
140 foreach ($files as $path) {
141 $this->addFileOperation('removebackup', array($path));
145 // {{{ _deletePackageFiles()
148 * Delete a package's installed files, does not remove empty directories.
150 * @param string package name
151 * @param string channel name
152 * @param bool if true, then files are backed up first
153 * @return bool TRUE on success, or a PEAR error on failure
156 function _deletePackageFiles($package, $channel = false, $backup = false)
159 $channel = 'pear.php.net';
162 if (!strlen($package)) {
163 return $this->raiseError("No package to uninstall given");
166 if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
167 // to avoid race conditions, include all possible needed files
168 require_once 'PEAR/Task/Common.php';
169 require_once 'PEAR/Task/Replace.php';
170 require_once 'PEAR/Task/Unixeol.php';
171 require_once 'PEAR/Task/Windowseol.php';
172 require_once 'PEAR/PackageFile/v1.php';
173 require_once 'PEAR/PackageFile/v2.php';
174 require_once 'PEAR/PackageFile/Generator/v1.php';
175 require_once 'PEAR/PackageFile/Generator/v2.php';
178 $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
179 if ($filelist == null) {
180 return $this->raiseError("$channel/$package not installed");
184 foreach ($filelist as $file => $props) {
185 if (empty($props['installed_as'])) {
189 $path = $props['installed_as'];
191 $this->addFileOperation('backup', array($path));
195 $this->addFileOperation('delete', array($path));
206 // {{{ _installFile()
209 * @param string filename
210 * @param array attributes from <file> tag in package.xml
211 * @param string path to install the file in
212 * @param array options from command-line
215 function _installFile($file, $atts, $tmp_path, $options)
217 // {{{ return if this file is meant for another platform
219 if (!isset($this->_registry)) {
220 $this->_registry = &$this->config->getRegistry();
223 if (isset($atts['platform'])) {
225 $os = new OS_Guess();
228 if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
230 $platform = substr($atts['platform'], 1);
233 $platform = $atts['platform'];
236 if ((bool) $os->matchSignature($platform) === $negate) {
237 $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
238 return PEAR_INSTALLER_SKIPPED;
243 $channel = $this->pkginfo->getChannel();
244 // {{{ assemble the destination paths
245 switch ($atts['role']) {
248 $this->source_files++;
253 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
254 DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
255 unset($atts['baseinstalldir']);
259 $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
262 $dest_dir = $this->config->get('bin_dir', null, $channel);
265 return $this->raiseError("Invalid role `$atts[role]' for file $file");
268 $save_destdir = $dest_dir;
269 if (!empty($atts['baseinstalldir'])) {
270 $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
273 if (dirname($file) != '.' && empty($atts['install-as'])) {
274 $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
277 if (empty($atts['install-as'])) {
278 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
280 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
282 $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
284 // Clean up the DIRECTORY_SEPARATOR mess
285 $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
286 list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
287 array(DIRECTORY_SEPARATOR,
289 DIRECTORY_SEPARATOR),
290 array($dest_file, $orig_file));
291 $final_dest_file = $installed_as = $dest_file;
292 if (isset($this->_options['packagingroot'])) {
293 $installedas_dest_dir = dirname($final_dest_file);
294 $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
295 $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
297 $installedas_dest_dir = dirname($final_dest_file);
298 $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
301 $dest_dir = dirname($final_dest_file);
302 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
303 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
304 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
308 if (empty($this->_options['register-only']) &&
309 (!file_exists($dest_dir) || !is_dir($dest_dir))) {
310 if (!$this->mkDirHier($dest_dir)) {
311 return $this->raiseError("failed to mkdir $dest_dir",
312 PEAR_INSTALLER_FAILED);
314 $this->log(3, "+ mkdir $dest_dir");
317 // pretty much nothing happens if we are only registering the install
318 if (empty($this->_options['register-only'])) {
319 if (empty($atts['replacements'])) {
320 if (!file_exists($orig_file)) {
321 return $this->raiseError("file $orig_file does not exist",
322 PEAR_INSTALLER_FAILED);
325 if (!@copy($orig_file, $dest_file)) {
326 return $this->raiseError("failed to write $dest_file: $php_errormsg",
327 PEAR_INSTALLER_FAILED);
330 $this->log(3, "+ cp $orig_file $dest_file");
331 if (isset($atts['md5sum'])) {
332 $md5sum = md5_file($dest_file);
335 // {{{ file with replacements
336 if (!file_exists($orig_file)) {
337 return $this->raiseError("file does not exist",
338 PEAR_INSTALLER_FAILED);
341 $contents = file_get_contents($orig_file);
342 if ($contents === false) {
346 if (isset($atts['md5sum'])) {
347 $md5sum = md5($contents);
350 $subst_from = $subst_to = array();
351 foreach ($atts['replacements'] as $a) {
353 if ($a['type'] == 'php-const') {
354 if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
355 eval("\$to = $a[to];");
357 if (!isset($options['soft'])) {
358 $this->log(0, "invalid php-const replacement: $a[to]");
362 } elseif ($a['type'] == 'pear-config') {
363 if ($a['to'] == 'master_server') {
364 $chan = $this->_registry->getChannel($channel);
365 if (!PEAR::isError($chan)) {
366 $to = $chan->getServer();
368 $to = $this->config->get($a['to'], null, $channel);
371 $to = $this->config->get($a['to'], null, $channel);
374 if (!isset($options['soft'])) {
375 $this->log(0, "invalid pear-config replacement: $a[to]");
379 } elseif ($a['type'] == 'package-info') {
380 if ($t = $this->pkginfo->packageInfo($a['to'])) {
383 if (!isset($options['soft'])) {
384 $this->log(0, "invalid package-info replacement: $a[to]");
390 $subst_from[] = $a['from'];
395 $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
396 if (sizeof($subst_from)) {
397 $contents = str_replace($subst_from, $subst_to, $contents);
400 $wp = @fopen($dest_file, "wb");
401 if (!is_resource($wp)) {
402 return $this->raiseError("failed to create $dest_file: $php_errormsg",
403 PEAR_INSTALLER_FAILED);
406 if (@fwrite($wp, $contents) === false) {
407 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
408 PEAR_INSTALLER_FAILED);
416 if (isset($md5sum)) {
417 if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
418 $this->log(2, "md5sum ok: $final_dest_file");
420 if (empty($options['force'])) {
422 if (file_exists($dest_file)) {
426 if (!isset($options['ignore-errors'])) {
427 return $this->raiseError("bad md5sum for file $final_dest_file",
428 PEAR_INSTALLER_FAILED);
431 if (!isset($options['soft'])) {
432 $this->log(0, "warning : bad md5sum for file $final_dest_file");
435 if (!isset($options['soft'])) {
436 $this->log(0, "warning : bad md5sum for file $final_dest_file");
442 // {{{ set file permissions
444 if ($atts['role'] == 'script') {
445 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
446 $this->log(3, "+ chmod +x $dest_file");
448 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
451 if ($atts['role'] != 'src') {
452 $this->addFileOperation("chmod", array($mode, $dest_file));
453 if (!@chmod($dest_file, $mode)) {
454 if (!isset($options['soft'])) {
455 $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
462 if ($atts['role'] == 'src') {
463 rename($dest_file, $final_dest_file);
464 $this->log(2, "renamed source file $dest_file to $final_dest_file");
466 $this->addFileOperation("rename", array($dest_file, $final_dest_file,
467 $atts['role'] == 'ext'));
471 // Store the full path where the file was installed for easy unistall
472 if ($atts['role'] != 'script') {
473 $loc = $this->config->get($atts['role'] . '_dir');
475 $loc = $this->config->get('bin_dir');
478 if ($atts['role'] != 'src') {
479 $this->addFileOperation("installed_as", array($file, $installed_as,
481 dirname(substr($installedas_dest_file, strlen($loc)))));
484 //$this->log(2, "installed: $dest_file");
485 return PEAR_INSTALLER_OK;
489 // {{{ _installFile2()
492 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
493 * @param string filename
494 * @param array attributes from <file> tag in package.xml
495 * @param string path to install the file in
496 * @param array options from command-line
499 function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
502 if (!isset($this->_registry)) {
503 $this->_registry = &$this->config->getRegistry();
506 $channel = $pkg->getChannel();
507 // {{{ assemble the destination paths
508 if (!in_array($atts['attribs']['role'],
509 PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
510 return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
514 $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
515 $err = $role->setup($this, $pkg, $atts['attribs'], $file);
516 if (PEAR::isError($err)) {
520 if (!$role->isInstallable()) {
524 $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
525 if (PEAR::isError($info)) {
529 list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
530 if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
531 return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
534 $final_dest_file = $installed_as = $dest_file;
535 if (isset($this->_options['packagingroot'])) {
536 $final_dest_file = $this->_prependPath($final_dest_file,
537 $this->_options['packagingroot']);
540 $dest_dir = dirname($final_dest_file);
541 $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
544 if (empty($this->_options['register-only'])) {
545 if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
546 if (!$this->mkDirHier($dest_dir)) {
547 return $this->raiseError("failed to mkdir $dest_dir",
548 PEAR_INSTALLER_FAILED);
550 $this->log(3, "+ mkdir $dest_dir");
554 $attribs = $atts['attribs'];
555 unset($atts['attribs']);
556 // pretty much nothing happens if we are only registering the install
557 if (empty($this->_options['register-only'])) {
558 if (!count($atts)) { // no tasks
559 if (!file_exists($orig_file)) {
560 return $this->raiseError("file $orig_file does not exist",
561 PEAR_INSTALLER_FAILED);
564 if (!@copy($orig_file, $dest_file)) {
565 return $this->raiseError("failed to write $dest_file: $php_errormsg",
566 PEAR_INSTALLER_FAILED);
569 $this->log(3, "+ cp $orig_file $dest_file");
570 if (isset($attribs['md5sum'])) {
571 $md5sum = md5_file($dest_file);
573 } else { // file with tasks
574 if (!file_exists($orig_file)) {
575 return $this->raiseError("file $orig_file does not exist",
576 PEAR_INSTALLER_FAILED);
579 $contents = file_get_contents($orig_file);
580 if ($contents === false) {
584 if (isset($attribs['md5sum'])) {
585 $md5sum = md5($contents);
588 foreach ($atts as $tag => $raw) {
589 $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
590 $task = "PEAR_Task_$tag";
591 $task = new $task($this->config, $this, PEAR_TASK_INSTALL);
592 if (!$task->isScript()) { // scripts are only handled after installation
593 $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
594 $res = $task->startSession($pkg, $contents, $final_dest_file);
595 if ($res === false) {
596 continue; // skip this file
599 if (PEAR::isError($res)) {
603 $contents = $res; // save changes
606 $wp = @fopen($dest_file, "wb");
607 if (!is_resource($wp)) {
608 return $this->raiseError("failed to create $dest_file: $php_errormsg",
609 PEAR_INSTALLER_FAILED);
612 if (fwrite($wp, $contents) === false) {
613 return $this->raiseError("failed writing to $dest_file: $php_errormsg",
614 PEAR_INSTALLER_FAILED);
622 if (isset($md5sum)) {
623 // Make sure the original md5 sum matches with expected
624 if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
625 $this->log(2, "md5sum ok: $final_dest_file");
627 if (isset($contents)) {
628 // set md5 sum based on $content in case any tasks were run.
629 $real_atts['attribs']['md5sum'] = md5($contents);
632 if (empty($options['force'])) {
634 if (file_exists($dest_file)) {
638 if (!isset($options['ignore-errors'])) {
639 return $this->raiseError("bad md5sum for file $final_dest_file",
640 PEAR_INSTALLER_FAILED);
643 if (!isset($options['soft'])) {
644 $this->log(0, "warning : bad md5sum for file $final_dest_file");
647 if (!isset($options['soft'])) {
648 $this->log(0, "warning : bad md5sum for file $final_dest_file");
653 $real_atts['attribs']['md5sum'] = md5_file($dest_file);
657 // {{{ set file permissions
659 if ($role->isExecutable()) {
660 $mode = 0777 & ~(int)octdec($this->config->get('umask'));
661 $this->log(3, "+ chmod +x $dest_file");
663 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
666 if ($attribs['role'] != 'src') {
667 $this->addFileOperation("chmod", array($mode, $dest_file));
668 if (!@chmod($dest_file, $mode)) {
669 if (!isset($options['soft'])) {
670 $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
677 if ($attribs['role'] == 'src') {
678 rename($dest_file, $final_dest_file);
679 $this->log(2, "renamed source file $dest_file to $final_dest_file");
681 $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
685 // Store the full path where the file was installed for easy uninstall
686 if ($attribs['role'] != 'src') {
687 $loc = $this->config->get($role->getLocationConfig(), null, $channel);
688 $this->addFileOperation('installed_as', array($file, $installed_as,
690 dirname(substr($installed_as, strlen($loc)))));
693 //$this->log(2, "installed: $dest_file");
694 return PEAR_INSTALLER_OK;
698 // {{{ addFileOperation()
701 * Add a file operation to the current file transaction.
703 * @see startFileTransaction()
704 * @param string $type This can be one of:
705 * - rename: rename a file ($data has 3 values)
706 * - backup: backup an existing file ($data has 1 value)
707 * - removebackup: clean up backups created during install ($data has 1 value)
708 * - chmod: change permissions on a file ($data has 2 values)
709 * - delete: delete a file ($data has 1 value)
710 * - rmdir: delete a directory if empty ($data has 1 value)
711 * - installed_as: mark a file as installed ($data has 4 values).
712 * @param array $data For all file operations, this array must contain the
713 * full path to the file or directory that is being operated on. For
714 * the rename command, the first parameter must be the file to rename,
715 * the second its new name, the third whether this is a PHP extension.
717 * The installed_as operation contains 4 elements in this order:
718 * 1. Filename as listed in the filelist element from package.xml
719 * 2. Full path to the installed file
720 * 3. Full path from the php_dir configuration variable used in this
722 * 4. Relative path from the php_dir that this file is installed in
724 function addFileOperation($type, $data)
726 if (!is_array($data)) {
727 return $this->raiseError('Internal Error: $data in addFileOperation'
728 . ' must be an array, was ' . gettype($data));
731 if ($type == 'chmod') {
732 $octmode = decoct($data[0]);
733 $this->log(3, "adding to transaction: $type $octmode $data[1]");
735 $this->log(3, "adding to transaction: $type " . implode(" ", $data));
737 $this->file_operations[] = array($type, $data);
741 // {{{ startFileTransaction()
743 function startFileTransaction($rollback_in_case = false)
745 if (count($this->file_operations) && $rollback_in_case) {
746 $this->rollbackFileTransaction();
748 $this->file_operations = array();
752 // {{{ commitFileTransaction()
754 function commitFileTransaction()
756 // {{{ first, check permissions and such manually
758 foreach ($this->file_operations as $key => $tr) {
759 list($type, $data) = $tr;
762 if (!file_exists($data[0])) {
763 $errors[] = "cannot rename file $data[0], doesn't exist";
766 // check that dest dir. is writable
767 if (!is_writable(dirname($data[1]))) {
768 $errors[] = "permission denied ($type): $data[1]";
772 // check that file is writable
773 if (!is_writable($data[1])) {
774 $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
778 if (!file_exists($data[0])) {
779 $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
781 // check that directory is writable
782 if (file_exists($data[0])) {
783 if (!is_writable(dirname($data[0]))) {
784 $errors[] = "permission denied ($type): $data[0]";
786 // make sure the file to be deleted can be opened for writing
788 if (!is_dir($data[0]) &&
789 (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
790 $errors[] = "permission denied ($type): $data[0]";
796 /* Verify we are not deleting a file owned by another package
797 * This can happen when a file moves from package A to B in
798 * an upgrade ala http://pear.php.net/17986
801 'package' => strtolower($this->pkginfo->getName()),
802 'channel' => strtolower($this->pkginfo->getChannel()),
804 $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
805 if (is_array($result)) {
806 $res = array_diff($result, $info);
808 $new = $this->_registry->getPackage($result[1], $result[0]);
809 $this->file_operations[$key] = false;
810 $pkginfoName = $this->pkginfo->getName();
811 $newChannel = $new->getChannel();
812 $newPackage = $new->getName();
813 $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled.");
823 $n = count($this->file_operations);
824 $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
828 foreach ($errors as $error) {
829 if (!isset($this->_options['soft'])) {
830 $this->log(1, $error);
834 if (!isset($this->_options['ignore-errors'])) {
839 $this->_dirtree = array();
840 // {{{ really commit the transaction
841 foreach ($this->file_operations as $i => $tr) {
843 // support removal of non-existing backups
847 list($type, $data) = $tr;
850 if (!file_exists($data[0])) {
851 $this->file_operations[$i] = false;
855 if (!@copy($data[0], $data[0] . '.bak')) {
856 $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
857 '.bak ' . $php_errormsg);
860 $this->log(3, "+ backup $data[0] to $data[0].bak");
863 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
864 unlink($data[0] . '.bak');
865 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
869 $test = file_exists($data[1]) ? @unlink($data[1]) : null;
870 if (!$test && file_exists($data[1])) {
872 $extra = ', this extension must be installed manually. Rename to "' .
873 basename($data[1]) . '"';
878 if (!isset($this->_options['soft'])) {
879 $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
883 if (!isset($this->_options['ignore-errors'])) {
888 // permissions issues with rename - copy() is far superior
889 $perms = @fileperms($data[0]);
890 if (!@copy($data[0], $data[1])) {
891 $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
892 ' ' . $php_errormsg);
896 // copy over permissions, otherwise they are lost
897 @chmod($data[1], $perms);
899 $this->log(3, "+ mv $data[0] $data[1]");
902 if (!@chmod($data[1], $data[0])) {
903 $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
904 decoct($data[0]) . ' ' . $php_errormsg);
908 $octmode = decoct($data[0]);
909 $this->log(3, "+ chmod $octmode $data[1]");
912 if (file_exists($data[0])) {
913 if (!@unlink($data[0])) {
914 $this->log(1, 'Could not delete ' . $data[0] . ' ' .
918 $this->log(3, "+ rm $data[0]");
922 if (file_exists($data[0])) {
924 $testme = opendir($data[0]);
925 while (false !== ($entry = readdir($testme))) {
926 if ($entry == '.' || $entry == '..') {
930 break 2; // this directory is not empty and can't be
935 if (!@rmdir($data[0])) {
936 $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
940 $this->log(3, "+ rmdir $data[0]");
945 $this->pkginfo->setInstalledAs($data[0], $data[1]);
946 if (!isset($this->_dirtree[dirname($data[1])])) {
947 $this->_dirtree[dirname($data[1])] = true;
948 $this->pkginfo->setDirtree(dirname($data[1]));
950 while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
951 $data[3] != '/' && $data[3] != '\\') {
952 $this->pkginfo->setDirtree($pp =
953 $this->_prependPath($data[3], $data[2]));
954 $this->_dirtree[$pp] = true;
955 $data[3] = dirname($data[3]);
962 $this->log(2, "successfully committed $n file operations");
963 $this->file_operations = array();
968 // {{{ rollbackFileTransaction()
970 function rollbackFileTransaction()
972 $n = count($this->file_operations);
973 $this->log(2, "rolling back $n file operations");
974 foreach ($this->file_operations as $tr) {
975 list($type, $data) = $tr;
978 if (file_exists($data[0] . '.bak')) {
979 if (file_exists($data[0] && is_writable($data[0]))) {
982 @copy($data[0] . '.bak', $data[0]);
983 $this->log(3, "+ restore $data[0] from $data[0].bak");
987 if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
988 unlink($data[0] . '.bak');
989 $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
994 $this->log(3, "+ rm $data[0]");
998 $this->log(3, "+ rmdir $data[0]");
1004 case 'installed_as':
1005 $this->pkginfo->setInstalledAs($data[0], false);
1009 $this->pkginfo->resetDirtree();
1010 $this->file_operations = array();
1014 // {{{ mkDirHier($dir)
1016 function mkDirHier($dir)
1018 $this->addFileOperation('mkdir', array($dir));
1019 return parent::mkDirHier($dir);
1023 // {{{ _parsePackageXml()
1025 function _parsePackageXml(&$descfile)
1027 // Parse xml file -----------------------------------------------
1028 $pkg = new PEAR_PackageFile($this->config, $this->debug);
1029 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1030 $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
1031 PEAR::staticPopErrorHandling();
1032 if (PEAR::isError($p)) {
1033 if (is_array($p->getUserInfo())) {
1034 foreach ($p->getUserInfo() as $err) {
1035 $loglevel = $err['level'] == 'error' ? 0 : 1;
1036 if (!isset($this->_options['soft'])) {
1037 $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
1041 return $this->raiseError('Installation failed: invalid package file');
1044 $descfile = $p->getPackageFile();
1050 * Set the list of PEAR_Downloader_Package objects to allow more sane
1051 * dependency validation
1054 function setDownloadedPackages(&$pkgs)
1056 PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1057 $err = $this->analyzeDependencies($pkgs);
1058 PEAR::popErrorHandling();
1059 if (PEAR::isError($err)) {
1062 $this->_downloadedPackages = &$pkgs;
1066 * Set the list of PEAR_Downloader_Package objects to allow more sane
1067 * dependency validation
1070 function setUninstallPackages(&$pkgs)
1072 $this->_downloadedPackages = &$pkgs;
1075 function getInstallPackages()
1077 return $this->_downloadedPackages;
1083 * Installs the files within the package file specified.
1085 * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
1086 * or a pre-initialized packagefile object
1087 * @param array $options
1088 * recognized options:
1089 * - installroot : optional prefix directory for installation
1090 * - force : force installation
1091 * - register-only : update registry but don't install files
1092 * - upgrade : upgrade existing install
1093 * - soft : fail silently
1094 * - nodeps : ignore dependency conflicts/missing dependencies
1095 * - alldeps : install all dependencies
1096 * - onlyreqdeps : install only required dependencies
1098 * @return array|PEAR_Error package info if successful
1100 function install($pkgfile, $options = array())
1102 $this->_options = $options;
1103 $this->_registry = &$this->config->getRegistry();
1104 if (is_object($pkgfile)) {
1106 $pkg = $pkgfile->getPackageFile();
1107 $pkgfile = $pkg->getArchiveFile();
1108 $descfile = $pkg->getPackageFile();
1110 $descfile = $pkgfile;
1111 $pkg = $this->_parsePackageXml($descfile);
1112 if (PEAR::isError($pkg)) {
1117 $tmpdir = dirname($descfile);
1118 if (realpath($descfile) != realpath($pkgfile)) {
1119 // Use the temp_dir since $descfile can contain the download dir path
1120 $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
1121 $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
1123 $tar = new Archive_Tar($pkgfile);
1124 if (!$tar->extract($tmpdir)) {
1125 return $this->raiseError("unable to unpack $pkgfile");
1129 $pkgname = $pkg->getName();
1130 $channel = $pkg->getChannel();
1132 if (isset($options['installroot'])) {
1133 $this->config->setInstallRoot($options['installroot']);
1134 $this->_registry = &$this->config->getRegistry();
1135 $installregistry = &$this->_registry;
1136 $this->installroot = ''; // all done automagically now
1137 $php_dir = $this->config->get('php_dir', null, $channel);
1139 $this->config->setInstallRoot(false);
1140 $this->_registry = &$this->config->getRegistry();
1141 if (isset($this->_options['packagingroot'])) {
1142 $regdir = $this->_prependPath(
1143 $this->config->get('php_dir', null, 'pear.php.net'),
1144 $this->_options['packagingroot']);
1146 $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net');
1147 if ($metadata_dir) {
1148 $metadata_dir = $this->_prependPath(
1150 $this->_options['packagingroot']);
1152 $packrootphp_dir = $this->_prependPath(
1153 $this->config->get('php_dir', null, $channel),
1154 $this->_options['packagingroot']);
1156 $installregistry = new PEAR_Registry($regdir, false, false, $metadata_dir);
1157 if (!$installregistry->channelExists($channel, true)) {
1158 // we need to fake a channel-discover of this channel
1159 $chanobj = $this->_registry->getChannel($channel, true);
1160 $installregistry->addChannel($chanobj);
1162 $php_dir = $packrootphp_dir;
1164 $installregistry = &$this->_registry;
1165 $php_dir = $this->config->get('php_dir', null, $channel);
1167 $this->installroot = '';
1170 // {{{ checks to do when not in "force" mode
1171 if (empty($options['force']) &&
1172 (file_exists($this->config->get('php_dir')) &&
1173 is_dir($this->config->get('php_dir')))) {
1174 $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
1175 $instfilelist = $pkg->getInstallationFileList(true);
1176 if (PEAR::isError($instfilelist)) {
1177 return $instfilelist;
1180 // ensure we have the most accurate registry
1181 $installregistry->flushFileMap();
1182 $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
1183 if (PEAR::isError($test)) {
1187 if (sizeof($test)) {
1188 $pkgs = $this->getInstallPackages();
1190 foreach ($pkgs as $param) {
1191 if ($pkg->isSubpackageOf($param)) {
1198 // subpackages can conflict with earlier versions of parent packages
1199 $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
1201 foreach ($tmp as $file => $info) {
1202 if (is_array($info)) {
1203 if (strtolower($info[1]) == strtolower($param->getPackage()) &&
1204 strtolower($info[0]) == strtolower($param->getChannel())
1206 if (isset($parentreg['filelist'][$file])) {
1207 unset($parentreg['filelist'][$file]);
1209 $pos = strpos($file, '/');
1210 $basedir = substr($file, 0, $pos);
1211 $file2 = substr($file, $pos + 1);
1212 if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1213 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1215 unset($parentreg['filelist'][$file2]);
1219 unset($test[$file]);
1222 if (strtolower($param->getChannel()) != 'pear.php.net') {
1226 if (strtolower($info) == strtolower($param->getPackage())) {
1227 if (isset($parentreg['filelist'][$file])) {
1228 unset($parentreg['filelist'][$file]);
1230 $pos = strpos($file, '/');
1231 $basedir = substr($file, 0, $pos);
1232 $file2 = substr($file, $pos + 1);
1233 if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1234 && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1236 unset($parentreg['filelist'][$file2]);
1240 unset($test[$file]);
1245 $pfk = new PEAR_PackageFile($this->config);
1246 $parentpkg = &$pfk->fromArray($parentreg);
1247 $installregistry->updatePackage2($parentpkg);
1250 if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
1252 foreach ($tmp as $file => $info) {
1253 if (is_string($info)) {
1254 // pear.php.net packages are always stored as strings
1255 if (strtolower($info) == strtolower($param->getPackage())) {
1256 // upgrading existing package
1257 unset($test[$file]);
1264 $msg = "$channel/$pkgname: conflicting files found:\n";
1265 $longest = max(array_map("strlen", array_keys($test)));
1266 $fmt = "%${longest}s (%s)\n";
1267 foreach ($test as $file => $info) {
1268 if (!is_array($info)) {
1269 $info = array('pear.php.net', $info);
1271 $info = $info[0] . '/' . $info[1];
1272 $msg .= sprintf($fmt, $file, $info);
1275 if (!isset($options['ignore-errors'])) {
1276 return $this->raiseError($msg);
1279 if (!isset($options['soft'])) {
1280 $this->log(0, "WARNING: $msg");
1287 $this->startFileTransaction();
1289 $usechannel = $channel;
1290 if ($channel == 'pecl.php.net') {
1291 $test = $installregistry->packageExists($pkgname, $channel);
1293 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1294 $usechannel = 'pear.php.net';
1297 $test = $installregistry->packageExists($pkgname, $channel);
1300 if (empty($options['upgrade']) && empty($options['soft'])) {
1301 // checks to do only when installing new packages
1302 if (empty($options['force']) && $test) {
1303 return $this->raiseError("$channel/$pkgname is already installed");
1308 $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1309 $v2 = $pkg->getVersion();
1310 $cmp = version_compare("$v1", "$v2", 'gt');
1311 if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
1312 return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
1317 // Do cleanups for upgrade and install, remove old release's files first
1318 if ($test && empty($options['register-only'])) {
1319 // when upgrading, remove old release's files first:
1320 if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
1322 if (!isset($options['ignore-errors'])) {
1323 return $this->raiseError($err);
1326 if (!isset($options['soft'])) {
1327 $this->log(0, 'WARNING: ' . $err->getMessage());
1334 // {{{ Copy files to dest dir ---------------------------------------
1336 // info from the package it self we want to access from _installFile
1337 $this->pkginfo = &$pkg;
1338 // used to determine whether we should build any C code
1339 $this->source_files = 0;
1341 $savechannel = $this->config->get('default_channel');
1342 if (empty($options['register-only']) && !is_dir($php_dir)) {
1343 if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
1344 return $this->raiseError("no installation destination directory '$php_dir'\n");
1348 if (substr($pkgfile, -4) != '.xml') {
1349 $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
1352 $this->configSet('default_channel', $channel);
1353 // {{{ install files
1355 $ver = $pkg->getPackagexmlVersion();
1356 if (version_compare($ver, '2.0', '>=')) {
1357 $filelist = $pkg->getInstallationFilelist();
1359 $filelist = $pkg->getFileList();
1362 if (PEAR::isError($filelist)) {
1366 $p = &$installregistry->getPackage($pkgname, $channel);
1367 $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
1369 $pkg->resetFilelist();
1370 $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
1371 'version', $pkg->getChannel()));
1372 foreach ($filelist as $file => $atts) {
1373 $this->expectError(PEAR_INSTALLER_FAILED);
1374 if ($pkg->getPackagexmlVersion() == '1.0') {
1375 $res = $this->_installFile($file, $atts, $tmpdir, $options);
1377 $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
1381 if (PEAR::isError($res)) {
1382 if (empty($options['ignore-errors'])) {
1383 $this->rollbackFileTransaction();
1384 if ($res->getMessage() == "file does not exist") {
1385 $this->raiseError("file $file in package.xml does not exist");
1388 return $this->raiseError($res);
1391 if (!isset($options['soft'])) {
1392 $this->log(0, "Warning: " . $res->getMessage());
1396 $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
1397 if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
1398 // Register files that were installed
1399 $pkg->installedFile($file, $atts);
1404 // {{{ compile and install source files
1405 if ($this->source_files > 0 && empty($options['nobuild'])) {
1406 if (PEAR::isError($err =
1407 $this->_compileSourceFiles($savechannel, $pkg))) {
1413 if (isset($backedup)) {
1414 $this->_removeBackups($backedup);
1417 if (!$this->commitFileTransaction()) {
1418 $this->rollbackFileTransaction();
1419 $this->configSet('default_channel', $savechannel);
1420 return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
1425 $installphase = 'install';
1426 $oldversion = false;
1427 // {{{ Register that the package is installed -----------------------
1428 if (empty($options['upgrade'])) {
1429 // if 'force' is used, replace the info in registry
1430 $usechannel = $channel;
1431 if ($channel == 'pecl.php.net') {
1432 $test = $installregistry->packageExists($pkgname, $channel);
1434 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1435 $usechannel = 'pear.php.net';
1438 $test = $installregistry->packageExists($pkgname, $channel);
1441 if (!empty($options['force']) && $test) {
1442 $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1443 $installregistry->deletePackage($pkgname, $usechannel);
1445 $ret = $installregistry->addPackage2($pkg);
1448 $this->startFileTransaction();
1449 // attempt to delete empty directories
1450 uksort($dirtree, array($this, '_sortDirs'));
1451 foreach($dirtree as $dir => $notused) {
1452 $this->addFileOperation('rmdir', array($dir));
1454 $this->commitFileTransaction();
1457 $usechannel = $channel;
1458 if ($channel == 'pecl.php.net') {
1459 $test = $installregistry->packageExists($pkgname, $channel);
1461 $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1462 $usechannel = 'pear.php.net';
1465 $test = $installregistry->packageExists($pkgname, $channel);
1468 // new: upgrade installs a package if it isn't installed
1470 $ret = $installregistry->addPackage2($pkg);
1472 if ($usechannel != $channel) {
1473 $installregistry->deletePackage($pkgname, $usechannel);
1474 $ret = $installregistry->addPackage2($pkg);
1476 $ret = $installregistry->updatePackage2($pkg);
1478 $installphase = 'upgrade';
1483 $this->configSet('default_channel', $savechannel);
1484 return $this->raiseError("Adding package $channel/$pkgname to registry failed");
1488 $this->configSet('default_channel', $savechannel);
1489 if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
1490 if (PEAR_Task_Common::hasPostinstallTasks()) {
1491 PEAR_Task_Common::runPostinstallTasks($installphase);
1495 return $pkg->toArray(true);
1500 // {{{ _compileSourceFiles()
1503 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1505 function _compileSourceFiles($savechannel, &$filelist)
1507 require_once 'PEAR/Builder.php';
1508 $this->log(1, "$this->source_files source files, building");
1509 $bob = new PEAR_Builder($this->ui);
1510 $bob->debug = $this->debug;
1511 $built = $bob->build($filelist, array(&$this, '_buildCallback'));
1512 if (PEAR::isError($built)) {
1513 $this->rollbackFileTransaction();
1514 $this->configSet('default_channel', $savechannel);
1518 $this->log(1, "\nBuild process completed successfully");
1519 foreach ($built as $ext) {
1520 $bn = basename($ext['file']);
1521 list($_ext_name, $_ext_suff) = explode('.', $bn);
1522 if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
1523 if (extension_loaded($_ext_name)) {
1524 $this->raiseError("Extension '$_ext_name' already loaded. " .
1525 'Please unload it in your php.ini file ' .
1526 'prior to install or upgrade');
1533 $dest = $ext['dest'];
1534 $packagingroot = '';
1535 if (isset($this->_options['packagingroot'])) {
1536 $packagingroot = $this->_options['packagingroot'];
1539 $copyto = $this->_prependPath($dest, $packagingroot);
1540 $extra = $copyto != $dest ? " as '$copyto'" : '';
1541 $this->log(1, "Installing '$dest'$extra");
1543 $copydir = dirname($copyto);
1544 // pretty much nothing happens if we are only registering the install
1545 if (empty($this->_options['register-only'])) {
1546 if (!file_exists($copydir) || !is_dir($copydir)) {
1547 if (!$this->mkDirHier($copydir)) {
1548 return $this->raiseError("failed to mkdir $copydir",
1549 PEAR_INSTALLER_FAILED);
1552 $this->log(3, "+ mkdir $copydir");
1555 if (!@copy($ext['file'], $copyto)) {
1556 return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
1559 $this->log(3, "+ cp $ext[file] $copyto");
1560 $this->addFileOperation('rename', array($ext['file'], $copyto));
1562 $mode = 0666 & ~(int)octdec($this->config->get('umask'));
1563 $this->addFileOperation('chmod', array($mode, $copyto));
1564 if (!@chmod($copyto, $mode)) {
1565 $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
1574 'installed_as' => $dest,
1575 'php_api' => $ext['php_api'],
1576 'zend_mod_api' => $ext['zend_mod_api'],
1577 'zend_ext_api' => $ext['zend_ext_api'],
1580 if ($filelist->getPackageXmlVersion() == '1.0') {
1581 $filelist->installedFile($bn, $data);
1583 $filelist->installedFile($bn, array('attribs' => $data));
1589 function &getUninstallPackages()
1591 return $this->_downloadedPackages;
1596 * Uninstall a package
1598 * This method removes all files installed by the application, and then
1599 * removes any empty directories.
1600 * @param string package name
1601 * @param array Command-line options. Possibilities include:
1603 * - installroot: base installation dir, if not the default
1604 * - register-only : update registry but don't remove files
1605 * - nodeps: do not process dependencies of other packages to ensure
1606 * uninstallation does not break things
1608 function uninstall($package, $options = array())
1610 $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
1611 $this->config->setInstallRoot($installRoot);
1613 $this->installroot = '';
1614 $this->_registry = &$this->config->getRegistry();
1615 if (is_object($package)) {
1616 $channel = $package->getChannel();
1618 $package = $pkg->getPackage();
1621 $info = $this->_registry->parsePackageName($package,
1622 $this->config->get('default_channel'));
1623 $channel = $info['channel'];
1624 $package = $info['package'];
1627 $savechannel = $this->config->get('default_channel');
1628 $this->configSet('default_channel', $channel);
1629 if (!is_object($pkg)) {
1630 $pkg = $this->_registry->getPackage($package, $channel);
1634 $this->configSet('default_channel', $savechannel);
1635 return $this->raiseError($this->_registry->parsedPackageNameToString(
1637 'channel' => $channel,
1638 'package' => $package
1639 ), true) . ' not installed');
1642 if ($pkg->getInstalledBinary()) {
1643 // this is just an alias for a binary package
1644 return $this->_registry->deletePackage($package, $channel);
1647 $filelist = $pkg->getFilelist();
1648 PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1649 if (!class_exists('PEAR_Dependency2')) {
1650 require_once 'PEAR/Dependency2.php';
1653 $depchecker = new PEAR_Dependency2($this->config, $options,
1654 array('channel' => $channel, 'package' => $package),
1655 PEAR_VALIDATE_UNINSTALLING);
1656 $e = $depchecker->validatePackageUninstall($this);
1657 PEAR::staticPopErrorHandling();
1658 if (PEAR::isError($e)) {
1659 if (!isset($options['ignore-errors'])) {
1660 return $this->raiseError($e);
1663 if (!isset($options['soft'])) {
1664 $this->log(0, 'WARNING: ' . $e->getMessage());
1666 } elseif (is_array($e)) {
1667 if (!isset($options['soft'])) {
1668 $this->log(0, $e[0]);
1672 $this->pkginfo = &$pkg;
1673 // pretty much nothing happens if we are only registering the uninstall
1674 if (empty($options['register-only'])) {
1675 // {{{ Delete the files
1676 $this->startFileTransaction();
1677 PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1678 if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
1679 PEAR::popErrorHandling();
1680 $this->rollbackFileTransaction();
1681 $this->configSet('default_channel', $savechannel);
1682 if (!isset($options['ignore-errors'])) {
1683 return $this->raiseError($err);
1686 if (!isset($options['soft'])) {
1687 $this->log(0, 'WARNING: ' . $err->getMessage());
1690 PEAR::popErrorHandling();
1693 if (!$this->commitFileTransaction()) {
1694 $this->rollbackFileTransaction();
1695 if (!isset($options['ignore-errors'])) {
1696 return $this->raiseError("uninstall failed");
1699 if (!isset($options['soft'])) {
1700 $this->log(0, 'WARNING: uninstall failed');
1703 $this->startFileTransaction();
1704 $dirtree = $pkg->getDirTree();
1705 if ($dirtree === false) {
1706 $this->configSet('default_channel', $savechannel);
1707 return $this->_registry->deletePackage($package, $channel);
1710 // attempt to delete empty directories
1711 uksort($dirtree, array($this, '_sortDirs'));
1712 foreach($dirtree as $dir => $notused) {
1713 $this->addFileOperation('rmdir', array($dir));
1716 if (!$this->commitFileTransaction()) {
1717 $this->rollbackFileTransaction();
1718 if (!isset($options['ignore-errors'])) {
1719 return $this->raiseError("uninstall failed");
1722 if (!isset($options['soft'])) {
1723 $this->log(0, 'WARNING: uninstall failed');
1730 $this->configSet('default_channel', $savechannel);
1731 // Register that the package is no longer installed
1732 return $this->_registry->deletePackage($package, $channel);
1736 * Sort a list of arrays of array(downloaded packagefilename) by dependency.
1738 * It also removes duplicate dependencies
1739 * @param array an array of PEAR_PackageFile_v[1/2] objects
1740 * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
1742 function sortPackagesForUninstall(&$packages)
1744 $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
1745 if (PEAR::isError($this->_dependencyDB)) {
1746 return $this->_dependencyDB;
1748 usort($packages, array(&$this, '_sortUninstall'));
1751 function _sortUninstall($a, $b)
1753 if (!$a->getDeps() && !$b->getDeps()) {
1754 return 0; // neither package has dependencies, order is insignificant
1756 if ($a->getDeps() && !$b->getDeps()) {
1757 return -1; // $a must be installed after $b because $a has dependencies
1759 if (!$a->getDeps() && $b->getDeps()) {
1760 return 1; // $b must be installed after $a because $b has dependencies
1762 // both packages have dependencies
1763 if ($this->_dependencyDB->dependsOn($a, $b)) {
1766 if ($this->_dependencyDB->dependsOn($b, $a)) {
1774 function _sortDirs($a, $b)
1776 if (strnatcmp($a, $b) == -1) return 1;
1777 if (strnatcmp($a, $b) == 1) return -1;
1783 // {{{ _buildCallback()
1785 function _buildCallback($what, $data)
1787 if (($what == 'cmdoutput' && $this->debug > 1) ||
1788 ($what == 'output' && $this->debug > 0)) {
1789 $this->ui->outputData(rtrim($data), 'build');