Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
 */
23
require_once 'OS/Guess.php';
24
require_once 'PEAR/Downloader.php';
25
 
26
define('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
 */
43
class 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
}