source: trunk/library/PEAR/PEAR/Installer.php @ 7681

Revision 7681, 68.8 KB checked in by douglasz, 11 years ago (diff)

Ticket #3236 - Correcoes para Best Practice: Short Open Tag e Best Practice: Always Quote Array Keys.

Line 
1<?php
2/**
3 * PEAR_Installer
4 *
5 * PHP versions 4 and 5
6 *
7 * @category   pear
8 * @package    PEAR
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
18 */
19
20/**
21 * Used for installation groups in package.xml 2.0 and platform exceptions
22 */
23require_once 'OS/Guess.php';
24require_once 'PEAR/Downloader.php';
25
26define('PEAR_INSTALLER_NOBINARY', -240);
27/**
28 * Administration class used to install PEAR packages and maintain the
29 * installed package database.
30 *
31 * @category   pear
32 * @package    PEAR
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
42 */
43class PEAR_Installer extends PEAR_Downloader
44{
45    // {{{ properties
46
47    /** name of the package directory, for example Foo-1.0
48     * @var string
49     */
50    var $pkgdir;
51
52    /** directory where PHP code files go
53     * @var string
54     */
55    var $phpdir;
56
57    /** directory where PHP extension files go
58     * @var string
59     */
60    var $extdir;
61
62    /** directory where documentation goes
63     * @var string
64     */
65    var $docdir;
66
67    /** installation root directory (ala PHP's INSTALL_ROOT or
68     * automake's DESTDIR
69     * @var string
70     */
71    var $installroot = '';
72
73    /** debug level
74     * @var int
75     */
76    var $debug = 1;
77
78    /** temporary directory
79     * @var string
80     */
81    var $tmpdir;
82
83    /**
84     * PEAR_Registry object used by the installer
85     * @var PEAR_Registry
86     */
87    var $registry;
88
89    /**
90     * array of PEAR_Downloader_Packages
91     * @var array
92     */
93    var $_downloadedPackages;
94
95    /** List of file transactions queued for an install/upgrade/uninstall.
96     *
97     *  Format:
98     *    array(
99     *      0 => array("rename => array("from-file", "to-file")),
100     *      1 => array("delete" => array("file-to-delete")),
101     *      ...
102     *    )
103     *
104     * @var array
105     */
106    var $file_operations = array();
107
108    // }}}
109
110    // {{{ constructor
111
112    /**
113     * PEAR_Installer constructor.
114     *
115     * @param object $ui user interface object (instance of PEAR_Frontend_*)
116     *
117     * @access public
118     */
119    function PEAR_Installer(&$ui)
120    {
121        parent::PEAR_Common();
122        $this->setFrontendObject($ui);
123        $this->debug = $this->config->get('verbose');
124    }
125
126    function setOptions($options)
127    {
128        $this->_options = $options;
129    }
130
131    function setConfig(&$config)
132    {
133        $this->config    = &$config;
134        $this->_registry = &$config->getRegistry();
135    }
136
137    // }}}
138
139    function _removeBackups($files)
140    {
141        foreach ($files as $path) {
142            $this->addFileOperation('removebackup', array($path));
143        }
144    }
145
146    // {{{ _deletePackageFiles()
147
148    /**
149     * Delete a package's installed files, does not remove empty directories.
150     *
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
155     * @access protected
156     */
157    function _deletePackageFiles($package, $channel = false, $backup = false)
158    {
159        if (!$channel) {
160            $channel = 'pear.php.net';
161        }
162
163        if (!strlen($package)) {
164            return $this->raiseError("No package to uninstall given");
165        }
166
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';
177        }
178
179        $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
180        if ($filelist == null) {
181            return $this->raiseError("$channel/$package not installed");
182        }
183
184        $ret = array();
185        foreach ($filelist as $file => $props) {
186            if (empty($props['installed_as'])) {
187                continue;
188            }
189
190            $path = $props['installed_as'];
191            if ($backup) {
192                $this->addFileOperation('backup', array($path));
193                $ret[] = $path;
194            }
195
196            $this->addFileOperation('delete', array($path));
197        }
198
199        if ($backup) {
200            return $ret;
201        }
202
203        return true;
204    }
205
206    // }}}
207    // {{{ _installFile()
208
209    /**
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
214     * @access private
215     */
216    function _installFile($file, $atts, $tmp_path, $options)
217    {
218        // {{{ return if this file is meant for another platform
219        static $os;
220        if (!isset($this->_registry)) {
221            $this->_registry = &$this->config->getRegistry();
222        }
223
224        if (isset($atts['platform'])) {
225            if (empty($os)) {
226                $os = new OS_Guess();
227            }
228
229            if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
230                $negate   = true;
231                $platform = substr($atts['platform'], 1);
232            } else {
233                $negate    = false;
234                $platform = $atts['platform'];
235            }
236
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;
240            }
241        }
242        // }}}
243
244        $channel = $this->pkginfo->getChannel();
245        // {{{ assemble the destination paths
246        switch ($atts['role']) {
247            case 'src':
248            case 'extsrc':
249                $this->source_files++;
250                return;
251            case 'doc':
252            case 'data':
253            case 'test':
254                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
255                            DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
256                unset($atts['baseinstalldir']);
257                break;
258            case 'ext':
259            case 'php':
260                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
261                break;
262            case 'script':
263                $dest_dir = $this->config->get('bin_dir', null, $channel);
264                break;
265            default:
266                return $this->raiseError("Invalid role '{$atts['role']}' for file $file");
267        }
268
269        $save_destdir = $dest_dir;
270        if (!empty($atts['baseinstalldir'])) {
271            $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
272        }
273
274        if (dirname($file) != '.' && empty($atts['install-as'])) {
275            $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
276        }
277
278        if (empty($atts['install-as'])) {
279            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
280        } else {
281            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
282        }
283        $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
284
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,
289                                                          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']);
297        } else {
298            $installedas_dest_dir  = dirname($final_dest_file);
299            $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
300        }
301
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);
306        }
307        // }}}
308
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);
314            }
315            $this->log(3, "+ mkdir $dest_dir");
316        }
317
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);
324                }
325
326                if (!@copy($orig_file, $dest_file)) {
327                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
328                                             PEAR_INSTALLER_FAILED);
329                }
330
331                $this->log(3, "+ cp $orig_file $dest_file");
332                if (isset($atts['md5sum'])) {
333                    $md5sum = md5_file($dest_file);
334                }
335            } else {
336                // {{{ file with replacements
337                if (!file_exists($orig_file)) {
338                    return $this->raiseError("file does not exist",
339                                             PEAR_INSTALLER_FAILED);
340                }
341
342                $contents = file_get_contents($orig_file);
343                if ($contents === false) {
344                    $contents = '';
345                }
346
347                if (isset($atts['md5sum'])) {
348                    $md5sum = md5($contents);
349                }
350
351                $subst_from = $subst_to = array();
352                foreach ($atts['replacements'] as $a) {
353                    $to = '';
354                    if ($a['type'] == 'php-const') {
355                        if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
356                            eval("\$to = {$a['to']};");
357                        } else {
358                            if (!isset($options['soft'])) {
359                                $this->log(0, "invalid php-const replacement: {$a['to']}");
360                            }
361                            continue;
362                        }
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();
368                            } else {
369                                $to = $this->config->get($a['to'], null, $channel);
370                            }
371                        } else {
372                            $to = $this->config->get($a['to'], null, $channel);
373                        }
374                        if (is_null($to)) {
375                            if (!isset($options['soft'])) {
376                                $this->log(0, "invalid pear-config replacement: {$a['to']}");
377                            }
378                            continue;
379                        }
380                    } elseif ($a['type'] == 'package-info') {
381                        if ($t = $this->pkginfo->packageInfo($a['to'])) {
382                            $to = $t;
383                        } else {
384                            if (!isset($options['soft'])) {
385                                $this->log(0, "invalid package-info replacement: {$a['to']}");
386                            }
387                            continue;
388                        }
389                    }
390                    if (!is_null($to)) {
391                        $subst_from[] = $a['from'];
392                        $subst_to[] = $to;
393                    }
394                }
395
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);
399                }
400
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);
405                }
406
407                if (@fwrite($wp, $contents) === false) {
408                    return $this->raiseError("failed writing to $dest_file: $php_errormsg",
409                                             PEAR_INSTALLER_FAILED);
410                }
411
412                fclose($wp);
413                // }}}
414            }
415
416            // {{{ check the md5
417            if (isset($md5sum)) {
418                if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
419                    $this->log(2, "md5sum ok: $final_dest_file");
420                } else {
421                    if (empty($options['force'])) {
422                        // delete the file
423                        if (file_exists($dest_file)) {
424                            unlink($dest_file);
425                        }
426
427                        if (!isset($options['ignore-errors'])) {
428                            return $this->raiseError("bad md5sum for file $final_dest_file",
429                                                 PEAR_INSTALLER_FAILED);
430                        }
431
432                        if (!isset($options['soft'])) {
433                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
434                        }
435                    } else {
436                        if (!isset($options['soft'])) {
437                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
438                        }
439                    }
440                }
441            }
442            // }}}
443            // {{{ set file permissions
444            if (!OS_WINDOWS) {
445                if ($atts['role'] == 'script') {
446                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
447                    $this->log(3, "+ chmod +x $dest_file");
448                } else {
449                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
450                }
451
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");
457                        }
458                    }
459                }
460            }
461            // }}}
462
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");
466            } else {
467                $this->addFileOperation("rename", array($dest_file, $final_dest_file,
468                    $atts['role'] == 'ext'));
469            }
470        }
471
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');
475        } else {
476            $loc = $this->config->get('bin_dir');
477        }
478
479        if ($atts['role'] != 'src') {
480            $this->addFileOperation("installed_as", array($file, $installed_as,
481                                    $loc,
482                                    dirname(substr($installedas_dest_file, strlen($loc)))));
483        }
484
485        //$this->log(2, "installed: $dest_file");
486        return PEAR_INSTALLER_OK;
487    }
488
489    // }}}
490    // {{{ _installFile2()
491
492    /**
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
498     * @access private
499     */
500    function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
501    {
502        $atts = $real_atts;
503        if (!isset($this->_registry)) {
504            $this->_registry = &$this->config->getRegistry();
505        }
506
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'] .
512                    "' for file $file");
513        }
514
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)) {
518            return $err;
519        }
520
521        if (!$role->isInstallable()) {
522            return;
523        }
524
525        $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
526        if (PEAR::isError($info)) {
527            return $info;
528        }
529
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);
533        }
534
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']);
539        }
540
541        $dest_dir  = dirname($final_dest_file);
542        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
543        // }}}
544
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);
550                }
551                $this->log(3, "+ mkdir $dest_dir");
552            }
553        }
554
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);
563                }
564
565                if (!@copy($orig_file, $dest_file)) {
566                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
567                                             PEAR_INSTALLER_FAILED);
568                }
569
570                $this->log(3, "+ cp $orig_file $dest_file");
571                if (isset($attribs['md5sum'])) {
572                    $md5sum = md5_file($dest_file);
573                }
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);
578                }
579
580                $contents = file_get_contents($orig_file);
581                if ($contents === false) {
582                    $contents = '';
583                }
584
585                if (isset($attribs['md5sum'])) {
586                    $md5sum = md5($contents);
587                }
588
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
598                        }
599
600                        if (PEAR::isError($res)) {
601                            return $res;
602                        }
603
604                        $contents = $res; // save changes
605                    }
606
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);
611                    }
612
613                    if (fwrite($wp, $contents) === false) {
614                        return $this->raiseError("failed writing to $dest_file: $php_errormsg",
615                                                 PEAR_INSTALLER_FAILED);
616                    }
617
618                    fclose($wp);
619                }
620            }
621
622            // {{{ check the md5
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");
627
628                    if (isset($contents)) {
629                        // set md5 sum based on $content in case any tasks were run.
630                        $real_atts['attribs']['md5sum'] = md5($contents);
631                    }
632                } else {
633                    if (empty($options['force'])) {
634                        // delete the file
635                        if (file_exists($dest_file)) {
636                            unlink($dest_file);
637                        }
638
639                        if (!isset($options['ignore-errors'])) {
640                            return $this->raiseError("bad md5sum for file $final_dest_file",
641                                                     PEAR_INSTALLER_FAILED);
642                        }
643
644                        if (!isset($options['soft'])) {
645                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
646                        }
647                    } else {
648                        if (!isset($options['soft'])) {
649                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
650                        }
651                    }
652                }
653            } else {
654                $real_atts['attribs']['md5sum'] = md5_file($dest_file);
655            }
656
657            // }}}
658            // {{{ set file permissions
659            if (!OS_WINDOWS) {
660                if ($role->isExecutable()) {
661                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
662                    $this->log(3, "+ chmod +x $dest_file");
663                } else {
664                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
665                }
666
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");
672                        }
673                    }
674                }
675            }
676            // }}}
677
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");
681            } else {
682                $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
683            }
684        }
685
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,
690                                $loc,
691                                dirname(substr($installed_as, strlen($loc)))));
692        }
693
694        //$this->log(2, "installed: $dest_file");
695        return PEAR_INSTALLER_OK;
696    }
697
698    // }}}
699    // {{{ addFileOperation()
700
701    /**
702     * Add a file operation to the current file transaction.
703     *
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.
717     *
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
722     *       installation
723     *    4. Relative path from the php_dir that this file is installed in
724     */
725    function addFileOperation($type, $data)
726    {
727        if (!is_array($data)) {
728            return $this->raiseError('Internal Error: $data in addFileOperation'
729                . ' must be an array, was ' . gettype($data));
730        }
731
732        if ($type == 'chmod') {
733            $octmode = decoct($data[0]);
734            $this->log(3, "adding to transaction: $type $octmode $data[1]");
735        } else {
736            $this->log(3, "adding to transaction: $type " . implode(" ", $data));
737        }
738        $this->file_operations[] = array($type, $data);
739    }
740
741    // }}}
742    // {{{ startFileTransaction()
743
744    function startFileTransaction($rollback_in_case = false)
745    {
746        if (count($this->file_operations) && $rollback_in_case) {
747            $this->rollbackFileTransaction();
748        }
749        $this->file_operations = array();
750    }
751
752    // }}}
753    // {{{ commitFileTransaction()
754
755    function commitFileTransaction()
756    {
757        // {{{ first, check permissions and such manually
758        $errors = array();
759        foreach ($this->file_operations as $key => $tr) {
760            list($type, $data) = $tr;
761            switch ($type) {
762                case 'rename':
763                    if (!file_exists($data[0])) {
764                        $errors[] = "cannot rename file $data[0], doesn't exist";
765                    }
766
767                    // check that dest dir. is writable
768                    if (!is_writable(dirname($data[1]))) {
769                        $errors[] = "permission denied ($type): $data[1]";
770                    }
771                    break;
772                case 'chmod':
773                    // check that file is writable
774                    if (!is_writable($data[1])) {
775                        $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
776                    }
777                    break;
778                case 'delete':
779                    if (!file_exists($data[0])) {
780                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
781                    }
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]";
786                        } else {
787                            // make sure the file to be deleted can be opened for writing
788                            $fp = false;
789                            if (!is_dir($data[0]) &&
790                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
791                                $errors[] = "permission denied ($type): $data[0]";
792                            } elseif ($fp) {
793                                fclose($fp);
794                            }
795                        }
796
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
800                         */
801                        $info = array(
802                            'package' => strtolower($this->pkginfo->getName()),
803                            'channel' => strtolower($this->pkginfo->getChannel()),
804                        );
805                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
806                        if (is_array($result)) {
807                            $res = array_diff($result, $info);
808                            if (!empty($res)) {
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.");
812                            }
813                        }
814                    }
815                    break;
816            }
817
818        }
819        // }}}
820
821        $n = count($this->file_operations);
822        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
823
824        $m = count($errors);
825        if ($m > 0) {
826            foreach ($errors as $error) {
827                if (!isset($this->_options['soft'])) {
828                    $this->log(1, $error);
829                }
830            }
831
832            if (!isset($this->_options['ignore-errors'])) {
833                return false;
834            }
835        }
836
837        $this->_dirtree = array();
838        // {{{ really commit the transaction
839        foreach ($this->file_operations as $i => $tr) {
840            if (!$tr) {
841                // support removal of non-existing backups
842                continue;
843            }
844
845            list($type, $data) = $tr;
846            switch ($type) {
847                case 'backup':
848                    if (!file_exists($data[0])) {
849                        $this->file_operations[$i] = false;
850                        break;
851                    }
852
853                    if (!@copy($data[0], $data[0] . '.bak')) {
854                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
855                            '.bak ' . $php_errormsg);
856                        return false;
857                    }
858                    $this->log(3, "+ backup $data[0] to $data[0].bak");
859                    break;
860                case 'removebackup':
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)");
864                    }
865                    break;
866                case 'rename':
867                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
868                    if (!$test && file_exists($data[1])) {
869                        if ($data[2]) {
870                            $extra = ', this extension must be installed manually.  Rename to "' .
871                                basename($data[1]) . '"';
872                        } else {
873                            $extra = '';
874                        }
875
876                        if (!isset($this->_options['soft'])) {
877                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
878                                $data[0] . $extra);
879                        }
880
881                        if (!isset($this->_options['ignore-errors'])) {
882                            return false;
883                        }
884                    }
885
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);
891                        return false;
892                    }
893
894                    // copy over permissions, otherwise they are lost
895                    @chmod($data[1], $perms);
896                    @unlink($data[0]);
897                    $this->log(3, "+ mv $data[0] $data[1]");
898                    break;
899                case 'chmod':
900                    if (!@chmod($data[1], $data[0])) {
901                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
902                            decoct($data[0]) . ' ' . $php_errormsg);
903                        return false;
904                    }
905
906                    $octmode = decoct($data[0]);
907                    $this->log(3, "+ chmod $octmode $data[1]");
908                    break;
909                case 'delete':
910                    if (file_exists($data[0])) {
911                        if (!@unlink($data[0])) {
912                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
913                                $php_errormsg);
914                            return false;
915                        }
916                        $this->log(3, "+ rm $data[0]");
917                    }
918                    break;
919                case 'rmdir':
920                    if (file_exists($data[0])) {
921                        do {
922                            $testme = opendir($data[0]);
923                            while (false !== ($entry = readdir($testme))) {
924                                if ($entry == '.' || $entry == '..') {
925                                    continue;
926                                }
927                                closedir($testme);
928                                break 2; // this directory is not empty and can't be
929                                         // deleted
930                            }
931
932                            closedir($testme);
933                            if (!@rmdir($data[0])) {
934                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
935                                    $php_errormsg);
936                                return false;
937                            }
938                            $this->log(3, "+ rmdir $data[0]");
939                        } while (false);
940                    }
941                    break;
942                case 'installed_as':
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]));
947
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]);
954                        }
955                    }
956                    break;
957            }
958        }
959        // }}}
960        $this->log(2, "successfully committed $n file operations");
961        $this->file_operations = array();
962        return true;
963    }
964
965    // }}}
966    // {{{ rollbackFileTransaction()
967
968    function rollbackFileTransaction()
969    {
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;
974            switch ($type) {
975                case 'backup':
976                    if (file_exists($data[0] . '.bak')) {
977                        if (file_exists($data[0] && is_writable($data[0]))) {
978                            unlink($data[0]);
979                        }
980                        @copy($data[0] . '.bak', $data[0]);
981                        $this->log(3, "+ restore $data[0] from $data[0].bak");
982                    }
983                    break;
984                case 'removebackup':
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)");
988                    }
989                    break;
990                case 'rename':
991                    @unlink($data[0]);
992                    $this->log(3, "+ rm $data[0]");
993                    break;
994                case 'mkdir':
995                    @rmdir($data[0]);
996                    $this->log(3, "+ rmdir $data[0]");
997                    break;
998                case 'chmod':
999                    break;
1000                case 'delete':
1001                    break;
1002                case 'installed_as':
1003                    $this->pkginfo->setInstalledAs($data[0], false);
1004                    break;
1005            }
1006        }
1007        $this->pkginfo->resetDirtree();
1008        $this->file_operations = array();
1009    }
1010
1011    // }}}
1012    // {{{ mkDirHier($dir)
1013
1014    function mkDirHier($dir)
1015    {
1016        $this->addFileOperation('mkdir', array($dir));
1017        return parent::mkDirHier($dir);
1018    }
1019
1020    // }}}
1021    // {{{ download()
1022
1023    /**
1024     * Download any files and their dependencies, if necessary
1025     *
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:
1031     *
1032     * <code>
1033     * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
1034     *    'info' => array() // parsed package.xml
1035     * );
1036     * </code>
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
1042     */
1043    function download($packages, $options, &$config, &$installpackages,
1044                      &$errors, $installed = false, $willinstall = false, $state = false)
1045    {
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);
1053        return $ret;
1054    }
1055
1056    // }}}
1057    // {{{ _parsePackageXml()
1058
1059    function _parsePackageXml(&$descfile)
1060    {
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']);
1072                    }
1073                }
1074            }
1075            return $this->raiseError('Installation failed: invalid package file');
1076        }
1077
1078        $descfile = $p->getPackageFile();
1079        return $p;
1080    }
1081
1082    // }}}
1083    /**
1084     * Set the list of PEAR_Downloader_Package objects to allow more sane
1085     * dependency validation
1086     * @param array
1087     */
1088    function setDownloadedPackages(&$pkgs)
1089    {
1090        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1091        $err = $this->analyzeDependencies($pkgs);
1092        PEAR::popErrorHandling();
1093        if (PEAR::isError($err)) {
1094            return $err;
1095        }
1096        $this->_downloadedPackages = &$pkgs;
1097    }
1098
1099    /**
1100     * Set the list of PEAR_Downloader_Package objects to allow more sane
1101     * dependency validation
1102     * @param array
1103     */
1104    function setUninstallPackages(&$pkgs)
1105    {
1106        $this->_downloadedPackages = &$pkgs;
1107    }
1108
1109    function getInstallPackages()
1110    {
1111        return $this->_downloadedPackages;
1112    }
1113
1114    // {{{ install()
1115
1116    /**
1117     * Installs the files within the package file specified.
1118     *
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
1131     *
1132     * @return array|PEAR_Error package info if successful
1133     */
1134    function install($pkgfile, $options = array())
1135    {
1136        $this->_options = $options;
1137        $this->_registry = &$this->config->getRegistry();
1138        if (is_object($pkgfile)) {
1139            $dlpkg    = &$pkgfile;
1140            $pkg      = $pkgfile->getPackageFile();
1141            $pkgfile  = $pkg->getArchiveFile();
1142            $descfile = $pkg->getPackageFile();
1143        } else {
1144            $descfile = $pkgfile;
1145            $pkg      = $this->_parsePackageXml($descfile);
1146            if (PEAR::isError($pkg)) {
1147                return $pkg;
1148            }
1149        }
1150
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 . '"');
1156
1157            $tar = new Archive_Tar($pkgfile);
1158            if (!$tar->extract($tmpdir)) {
1159                return $this->raiseError("unable to unpack $pkgfile");
1160            }
1161        }
1162
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']);
1169
1170            $packrootphp_dir = $this->_prependPath(
1171                $this->config->get('php_dir', null, $channel),
1172                $this->_options['packagingroot']);
1173        }
1174
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);
1181        } else {
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);
1190                }
1191                $php_dir = $packrootphp_dir;
1192            } else {
1193                $installregistry = &$this->_registry;
1194                $php_dir = $this->config->get('php_dir', null, $channel);
1195            }
1196            $this->installroot = '';
1197        }
1198
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;
1207            }
1208
1209            // ensure we have the most accurate registry
1210            $installregistry->flushFileMap();
1211            $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
1212            if (PEAR::isError($test)) {
1213                return $test;
1214            }
1215
1216            if (sizeof($test)) {
1217                $pkgs = $this->getInstallPackages();
1218                $found = false;
1219                foreach ($pkgs as $param) {
1220                    if ($pkg->isSubpackageOf($param)) {
1221                        $found = true;
1222                        break;
1223                    }
1224                }
1225
1226                if ($found) {
1227                    // subpackages can conflict with earlier versions of parent packages
1228                    $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
1229                    $tmp = $test;
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())
1234                            ) {
1235                                if (isset($parentreg['filelist'][$file])) {
1236                                    unset($parentreg['filelist'][$file]);
1237                                } else{
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
1243                                    ) {
1244                                        unset($parentreg['filelist'][$file2]);
1245                                    }
1246                                }
1247
1248                                unset($test[$file]);
1249                            }
1250                        } else {
1251                            if (strtolower($param->getChannel()) != 'pear.php.net') {
1252                                continue;
1253                            }
1254
1255                            if (strtolower($info) == strtolower($param->getPackage())) {
1256                                if (isset($parentreg['filelist'][$file])) {
1257                                    unset($parentreg['filelist'][$file]);
1258                                } else{
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
1264                                    ) {
1265                                        unset($parentreg['filelist'][$file2]);
1266                                    }
1267                                }
1268
1269                                unset($test[$file]);
1270                            }
1271                        }
1272                    }
1273
1274                    $pfk = &new PEAR_PackageFile($this->config);
1275                    $parentpkg = &$pfk->fromArray($parentreg);
1276                    $installregistry->updatePackage2($parentpkg);
1277                }
1278
1279                if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
1280                    $tmp = $test;
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]);
1287                            }
1288                        }
1289                    }
1290                }
1291
1292                if (count($test)) {
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);
1299                        }
1300                        $info = $info[0] . '/' . $info[1];
1301                        $msg .= sprintf($fmt, $file, $info);
1302                    }
1303
1304                    if (!isset($options['ignore-errors'])) {
1305                        return $this->raiseError($msg);
1306                    }
1307
1308                    if (!isset($options['soft'])) {
1309                        $this->log(0, "WARNING: $msg");
1310                    }
1311                }
1312            }
1313        }
1314        // }}}
1315
1316        $this->startFileTransaction();
1317
1318        $usechannel = $channel;
1319        if ($channel == 'pecl.php.net') {
1320            $test = $installregistry->packageExists($pkgname, $channel);
1321            if (!$test) {
1322                $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1323                $usechannel = 'pear.php.net';
1324            }
1325        } else {
1326            $test = $installregistry->packageExists($pkgname, $channel);
1327        }
1328
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");
1333            }
1334        } else {
1335            // Upgrade
1336            if ($test) {
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)");
1342                }
1343            }
1344        }
1345
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,
1350                  true))) {
1351                if (!isset($options['ignore-errors'])) {
1352                    return $this->raiseError($err);
1353                }
1354
1355                if (!isset($options['soft'])) {
1356                    $this->log(0, 'WARNING: ' . $err->getMessage());
1357                }
1358            } else {
1359                $backedup = $err;
1360            }
1361        }
1362
1363        // {{{ Copy files to dest dir ---------------------------------------
1364
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;
1369
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");
1374            }
1375        }
1376
1377        if (substr($pkgfile, -4) != '.xml') {
1378            $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
1379        }
1380
1381        $this->configSet('default_channel', $channel);
1382        // {{{ install files
1383
1384        $ver = $pkg->getPackagexmlVersion();
1385        if (version_compare($ver, '2.0', '>=')) {
1386            $filelist = $pkg->getInstallationFilelist();
1387        } else {
1388            $filelist = $pkg->getFileList();
1389        }
1390
1391        if (PEAR::isError($filelist)) {
1392            return $filelist;
1393        }
1394
1395        $p = &$installregistry->getPackage($pkgname, $channel);
1396        $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
1397
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);
1405            } else {
1406                $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
1407            }
1408            $this->popExpect();
1409
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");
1415                    }
1416
1417                    return $this->raiseError($res);
1418                }
1419
1420                if (!isset($options['soft'])) {
1421                    $this->log(0, "Warning: " . $res->getMessage());
1422                }
1423            }
1424
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);
1429            }
1430        }
1431        // }}}
1432
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))) {
1437                return $err;
1438            }
1439        }
1440        // }}}
1441
1442        if (isset($backedup)) {
1443            $this->_removeBackups($backedup);
1444        }
1445
1446        if (!$this->commitFileTransaction()) {
1447            $this->rollbackFileTransaction();
1448            $this->configSet('default_channel', $savechannel);
1449            return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
1450        }
1451        // }}}
1452
1453        $ret          = false;
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);
1462                if (!$test) {
1463                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1464                    $usechannel = 'pear.php.net';
1465                }
1466            } else {
1467                $test = $installregistry->packageExists($pkgname, $channel);
1468            }
1469
1470            if (!empty($options['force']) && $test) {
1471                $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1472                $installregistry->deletePackage($pkgname, $usechannel);
1473            }
1474            $ret = $installregistry->addPackage2($pkg);
1475        } else {
1476            if ($dirtree) {
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));
1482                }
1483                $this->commitFileTransaction();
1484            }
1485
1486            $usechannel = $channel;
1487            if ($channel == 'pecl.php.net') {
1488                $test = $installregistry->packageExists($pkgname, $channel);
1489                if (!$test) {
1490                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1491                    $usechannel = 'pear.php.net';
1492                }
1493            } else {
1494                $test = $installregistry->packageExists($pkgname, $channel);
1495            }
1496
1497            // new: upgrade installs a package if it isn't installed
1498            if (!$test) {
1499                $ret = $installregistry->addPackage2($pkg);
1500            } else {
1501                if ($usechannel != $channel) {
1502                    $installregistry->deletePackage($pkgname, $usechannel);
1503                    $ret = $installregistry->addPackage2($pkg);
1504                } else {
1505                    $ret = $installregistry->updatePackage2($pkg);
1506                }
1507                $installphase = 'upgrade';
1508            }
1509        }
1510
1511        if (!$ret) {
1512            $this->configSet('default_channel', $savechannel);
1513            return $this->raiseError("Adding package $channel/$pkgname to registry failed");
1514        }
1515        // }}}
1516
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);
1521            }
1522        }
1523
1524        return $pkg->toArray(true);
1525    }
1526
1527    // }}}
1528
1529    // {{{ _compileSourceFiles()
1530    /**
1531     * @param string
1532     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1533     */
1534    function _compileSourceFiles($savechannel, &$filelist)
1535    {
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);
1544            return $built;
1545        }
1546
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');
1556                }
1557                $role = 'ext';
1558            } else {
1559                $role = 'src';
1560            }
1561
1562            $dest = $ext['dest'];
1563            $packagingroot = '';
1564            if (isset($this->_options['packagingroot'])) {
1565                $packagingroot = $this->_options['packagingroot'];
1566            }
1567
1568            $copyto = $this->_prependPath($dest, $packagingroot);
1569            $extra  = $copyto != $dest ? " as '$copyto'" : '';
1570            $this->log(1, "Installing '$dest'$extra");
1571
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);
1579                    }
1580
1581                    $this->log(3, "+ mkdir $copydir");
1582                }
1583
1584                if (!@copy($ext['file'], $copyto)) {
1585                    return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
1586                }
1587
1588                $this->log(3, "+ cp {$ext['file']} $copyto");
1589                $this->addFileOperation('rename', array($ext['file'], $copyto));
1590                if (!OS_WINDOWS) {
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)");
1595                    }
1596                }
1597            }
1598
1599
1600            $data = array(
1601                'role'         => $role,
1602                'name'         => $bn,
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'],
1607            );
1608
1609            if ($filelist->getPackageXmlVersion() == '1.0') {
1610                $filelist->installedFile($bn, $data);
1611            } else {
1612                $filelist->installedFile($bn, array('attribs' => $data));
1613            }
1614        }
1615    }
1616
1617    // }}}
1618    function &getUninstallPackages()
1619    {
1620        return $this->_downloadedPackages;
1621    }
1622    // {{{ uninstall()
1623
1624    /**
1625     * Uninstall a package
1626     *
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:
1631     *
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
1636     */
1637    function uninstall($package, $options = array())
1638    {
1639        $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
1640        $this->config->setInstallRoot($installRoot);
1641
1642        $this->installroot = '';
1643        $this->_registry = &$this->config->getRegistry();
1644        if (is_object($package)) {
1645            $channel = $package->getChannel();
1646            $pkg     = $package;
1647            $package = $pkg->getPackage();
1648        } else {
1649            $pkg = false;
1650            $info = $this->_registry->parsePackageName($package,
1651                $this->config->get('default_channel'));
1652            $channel = $info['channel'];
1653            $package = $info['package'];
1654        }
1655
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);
1660        }
1661
1662        if (!$pkg) {
1663            $this->configSet('default_channel', $savechannel);
1664            return $this->raiseError($this->_registry->parsedPackageNameToString(
1665                array(
1666                    'channel' => $channel,
1667                    'package' => $package
1668                ), true) . ' not installed');
1669        }
1670
1671        if ($pkg->getInstalledBinary()) {
1672            // this is just an alias for a binary package
1673            return $this->_registry->deletePackage($package, $channel);
1674        }
1675
1676        $filelist = $pkg->getFilelist();
1677        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1678        if (!class_exists('PEAR_Dependency2')) {
1679            require_once 'PEAR/Dependency2.php';
1680        }
1681
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);
1690            }
1691
1692            if (!isset($options['soft'])) {
1693                $this->log(0, 'WARNING: ' . $e->getMessage());
1694            }
1695        } elseif (is_array($e)) {
1696            if (!isset($options['soft'])) {
1697                $this->log(0, $e[0]);
1698            }
1699        }
1700
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);
1713                }
1714
1715                if (!isset($options['soft'])) {
1716                    $this->log(0, 'WARNING: ' . $err->getMessage());
1717                }
1718            } else {
1719                PEAR::popErrorHandling();
1720            }
1721
1722            if (!$this->commitFileTransaction()) {
1723                $this->rollbackFileTransaction();
1724                if (!isset($options['ignore-errors'])) {
1725                    return $this->raiseError("uninstall failed");
1726                }
1727
1728                if (!isset($options['soft'])) {
1729                    $this->log(0, 'WARNING: uninstall failed');
1730                }
1731            } else {
1732                $this->startFileTransaction();
1733                $dirtree = $pkg->getDirTree();
1734                if ($dirtree === false) {
1735                    $this->configSet('default_channel', $savechannel);
1736                    return $this->_registry->deletePackage($package, $channel);
1737                }
1738
1739                // attempt to delete empty directories
1740                uksort($dirtree, array($this, '_sortDirs'));
1741                foreach($dirtree as $dir => $notused) {
1742                    $this->addFileOperation('rmdir', array($dir));
1743                }
1744
1745                if (!$this->commitFileTransaction()) {
1746                    $this->rollbackFileTransaction();
1747                    if (!isset($options['ignore-errors'])) {
1748                        return $this->raiseError("uninstall failed");
1749                    }
1750
1751                    if (!isset($options['soft'])) {
1752                        $this->log(0, 'WARNING: uninstall failed');
1753                    }
1754                }
1755            }
1756            // }}}
1757        }
1758
1759        $this->configSet('default_channel', $savechannel);
1760        // Register that the package is no longer installed
1761        return $this->_registry->deletePackage($package, $channel);
1762    }
1763
1764    /**
1765     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
1766     *
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)
1770     */
1771    function sortPackagesForUninstall(&$packages)
1772    {
1773        $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
1774        if (PEAR::isError($this->_dependencyDB)) {
1775            return $this->_dependencyDB;
1776        }
1777        usort($packages, array(&$this, '_sortUninstall'));
1778    }
1779
1780    function _sortUninstall($a, $b)
1781    {
1782        if (!$a->getDeps() && !$b->getDeps()) {
1783            return 0; // neither package has dependencies, order is insignificant
1784        }
1785        if ($a->getDeps() && !$b->getDeps()) {
1786            return -1; // $a must be installed after $b because $a has dependencies
1787        }
1788        if (!$a->getDeps() && $b->getDeps()) {
1789            return 1; // $b must be installed after $a because $b has dependencies
1790        }
1791        // both packages have dependencies
1792        if ($this->_dependencyDB->dependsOn($a, $b)) {
1793            return -1;
1794        }
1795        if ($this->_dependencyDB->dependsOn($b, $a)) {
1796            return 1;
1797        }
1798        return 0;
1799    }
1800
1801    // }}}
1802    // {{{ _sortDirs()
1803    function _sortDirs($a, $b)
1804    {
1805        if (strnatcmp($a, $b) == -1) return 1;
1806        if (strnatcmp($a, $b) == 1) return -1;
1807        return 0;
1808    }
1809
1810    // }}}
1811
1812    // {{{ _buildCallback()
1813
1814    function _buildCallback($what, $data)
1815    {
1816        if (($what == 'cmdoutput' && $this->debug > 1) ||
1817            ($what == 'output' && $this->debug > 0)) {
1818            $this->ui->outputData(rtrim($data), 'build');
1819        }
1820    }
1821
1822    // }}}
1823}
Note: See TracBrowser for help on using the repository browser.