Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
94 jpm 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>
187 mathias 13
 * @copyright  1997-2009 The Authors
14
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
94 jpm 15
 * @link       http://pear.php.net/package/PEAR
16
 * @since      File available since Release 0.1
17
 */
18
 
19
/**
20
 * Used for installation groups in package.xml 2.0 and platform exceptions
21
 */
22
require_once 'OS/Guess.php';
23
require_once 'PEAR/Downloader.php';
24
 
25
define('PEAR_INSTALLER_NOBINARY', -240);
26
/**
27
 * Administration class used to install PEAR packages and maintain the
28
 * installed package database.
29
 *
30
 * @category   pear
31
 * @package    PEAR
32
 * @author     Stig Bakken <ssb@php.net>
33
 * @author     Tomas V.V. Cox <cox@idecnet.com>
34
 * @author     Martin Jansen <mj@php.net>
35
 * @author     Greg Beaver <cellog@php.net>
187 mathias 36
 * @copyright  1997-2009 The Authors
37
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
38
 * @version    Release: 1.10.1
94 jpm 39
 * @link       http://pear.php.net/package/PEAR
40
 * @since      Class available since Release 0.1
41
 */
42
class PEAR_Installer extends PEAR_Downloader
43
{
44
    // {{{ properties
45
 
46
    /** name of the package directory, for example Foo-1.0
47
     * @var string
48
     */
49
    var $pkgdir;
50
 
51
    /** directory where PHP code files go
52
     * @var string
53
     */
54
    var $phpdir;
55
 
56
    /** directory where PHP extension files go
57
     * @var string
58
     */
59
    var $extdir;
60
 
61
    /** directory where documentation goes
62
     * @var string
63
     */
64
    var $docdir;
65
 
66
    /** installation root directory (ala PHP's INSTALL_ROOT or
67
     * automake's DESTDIR
68
     * @var string
69
     */
70
    var $installroot = '';
71
 
72
    /** debug level
73
     * @var int
74
     */
75
    var $debug = 1;
76
 
77
    /** temporary directory
78
     * @var string
79
     */
80
    var $tmpdir;
81
 
82
    /**
83
     * PEAR_Registry object used by the installer
84
     * @var PEAR_Registry
85
     */
86
    var $registry;
87
 
88
    /**
89
     * array of PEAR_Downloader_Packages
90
     * @var array
91
     */
92
    var $_downloadedPackages;
93
 
94
    /** List of file transactions queued for an install/upgrade/uninstall.
95
     *
96
     *  Format:
97
     *    array(
98
     *      0 => array("rename => array("from-file", "to-file")),
99
     *      1 => array("delete" => array("file-to-delete")),
100
     *      ...
101
     *    )
102
     *
103
     * @var array
104
     */
105
    var $file_operations = array();
106
 
107
    // }}}
108
 
109
    // {{{ constructor
110
 
111
    /**
112
     * PEAR_Installer constructor.
113
     *
114
     * @param object $ui user interface object (instance of PEAR_Frontend_*)
115
     *
116
     * @access public
117
     */
187 mathias 118
    function __construct(&$ui)
94 jpm 119
    {
187 mathias 120
        parent::__construct($ui, array(), null);
94 jpm 121
        $this->setFrontendObject($ui);
122
        $this->debug = $this->config->get('verbose');
123
    }
124
 
125
    function setOptions($options)
126
    {
127
        $this->_options = $options;
128
    }
129
 
130
    function setConfig(&$config)
131
    {
187 mathias 132
        $this->config    = &$config;
94 jpm 133
        $this->_registry = &$config->getRegistry();
134
    }
135
 
136
    // }}}
137
 
138
    function _removeBackups($files)
139
    {
140
        foreach ($files as $path) {
141
            $this->addFileOperation('removebackup', array($path));
142
        }
143
    }
144
 
145
    // {{{ _deletePackageFiles()
146
 
147
    /**
148
     * Delete a package's installed files, does not remove empty directories.
149
     *
150
     * @param string package name
151
     * @param string channel name
152
     * @param bool if true, then files are backed up first
153
     * @return bool TRUE on success, or a PEAR error on failure
154
     * @access protected
155
     */
156
    function _deletePackageFiles($package, $channel = false, $backup = false)
157
    {
158
        if (!$channel) {
159
            $channel = 'pear.php.net';
160
        }
187 mathias 161
 
94 jpm 162
        if (!strlen($package)) {
163
            return $this->raiseError("No package to uninstall given");
164
        }
187 mathias 165
 
94 jpm 166
        if (strtolower($package) == 'pear' && $channel == 'pear.php.net') {
167
            // to avoid race conditions, include all possible needed files
168
            require_once 'PEAR/Task/Common.php';
169
            require_once 'PEAR/Task/Replace.php';
170
            require_once 'PEAR/Task/Unixeol.php';
171
            require_once 'PEAR/Task/Windowseol.php';
172
            require_once 'PEAR/PackageFile/v1.php';
173
            require_once 'PEAR/PackageFile/v2.php';
174
            require_once 'PEAR/PackageFile/Generator/v1.php';
175
            require_once 'PEAR/PackageFile/Generator/v2.php';
176
        }
187 mathias 177
 
94 jpm 178
        $filelist = $this->_registry->packageInfo($package, 'filelist', $channel);
179
        if ($filelist == null) {
180
            return $this->raiseError("$channel/$package not installed");
181
        }
187 mathias 182
 
94 jpm 183
        $ret = array();
184
        foreach ($filelist as $file => $props) {
185
            if (empty($props['installed_as'])) {
186
                continue;
187
            }
187 mathias 188
 
94 jpm 189
            $path = $props['installed_as'];
190
            if ($backup) {
191
                $this->addFileOperation('backup', array($path));
192
                $ret[] = $path;
193
            }
187 mathias 194
 
94 jpm 195
            $this->addFileOperation('delete', array($path));
196
        }
187 mathias 197
 
94 jpm 198
        if ($backup) {
199
            return $ret;
200
        }
187 mathias 201
 
94 jpm 202
        return true;
203
    }
204
 
205
    // }}}
206
    // {{{ _installFile()
207
 
208
    /**
209
     * @param string filename
210
     * @param array attributes from <file> tag in package.xml
211
     * @param string path to install the file in
212
     * @param array options from command-line
213
     * @access private
214
     */
215
    function _installFile($file, $atts, $tmp_path, $options)
216
    {
217
        // {{{ return if this file is meant for another platform
218
        static $os;
219
        if (!isset($this->_registry)) {
220
            $this->_registry = &$this->config->getRegistry();
221
        }
187 mathias 222
 
94 jpm 223
        if (isset($atts['platform'])) {
224
            if (empty($os)) {
225
                $os = new OS_Guess();
226
            }
187 mathias 227
 
94 jpm 228
            if (strlen($atts['platform']) && $atts['platform']{0} == '!') {
187 mathias 229
                $negate   = true;
94 jpm 230
                $platform = substr($atts['platform'], 1);
231
            } else {
187 mathias 232
                $negate    = false;
94 jpm 233
                $platform = $atts['platform'];
234
            }
187 mathias 235
 
94 jpm 236
            if ((bool) $os->matchSignature($platform) === $negate) {
237
                $this->log(3, "skipped $file (meant for $atts[platform], we are ".$os->getSignature().")");
238
                return PEAR_INSTALLER_SKIPPED;
239
            }
240
        }
241
        // }}}
242
 
243
        $channel = $this->pkginfo->getChannel();
244
        // {{{ assemble the destination paths
245
        switch ($atts['role']) {
187 mathias 246
            case 'src':
247
            case 'extsrc':
248
                $this->source_files++;
249
                return;
94 jpm 250
            case 'doc':
251
            case 'data':
252
            case 'test':
253
                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel) .
254
                            DIRECTORY_SEPARATOR . $this->pkginfo->getPackage();
255
                unset($atts['baseinstalldir']);
256
                break;
257
            case 'ext':
258
            case 'php':
259
                $dest_dir = $this->config->get($atts['role'] . '_dir', null, $channel);
260
                break;
261
            case 'script':
262
                $dest_dir = $this->config->get('bin_dir', null, $channel);
263
                break;
264
            default:
265
                return $this->raiseError("Invalid role `$atts[role]' for file $file");
266
        }
187 mathias 267
 
94 jpm 268
        $save_destdir = $dest_dir;
269
        if (!empty($atts['baseinstalldir'])) {
270
            $dest_dir .= DIRECTORY_SEPARATOR . $atts['baseinstalldir'];
271
        }
187 mathias 272
 
94 jpm 273
        if (dirname($file) != '.' && empty($atts['install-as'])) {
274
            $dest_dir .= DIRECTORY_SEPARATOR . dirname($file);
275
        }
187 mathias 276
 
94 jpm 277
        if (empty($atts['install-as'])) {
278
            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . basename($file);
279
        } else {
280
            $dest_file = $dest_dir . DIRECTORY_SEPARATOR . $atts['install-as'];
281
        }
282
        $orig_file = $tmp_path . DIRECTORY_SEPARATOR . $file;
283
 
284
        // Clean up the DIRECTORY_SEPARATOR mess
285
        $ds2 = DIRECTORY_SEPARATOR . DIRECTORY_SEPARATOR;
286
        list($dest_file, $orig_file) = preg_replace(array('!\\\\+!', '!/!', "!$ds2+!"),
287
                                                    array(DIRECTORY_SEPARATOR,
288
                                                          DIRECTORY_SEPARATOR,
289
                                                          DIRECTORY_SEPARATOR),
290
                                                    array($dest_file, $orig_file));
291
        $final_dest_file = $installed_as = $dest_file;
292
        if (isset($this->_options['packagingroot'])) {
187 mathias 293
            $installedas_dest_dir  = dirname($final_dest_file);
94 jpm 294
            $installedas_dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
187 mathias 295
            $final_dest_file = $this->_prependPath($final_dest_file, $this->_options['packagingroot']);
94 jpm 296
        } else {
187 mathias 297
            $installedas_dest_dir  = dirname($final_dest_file);
94 jpm 298
            $installedas_dest_file = $installedas_dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
299
        }
187 mathias 300
 
301
        $dest_dir  = dirname($final_dest_file);
94 jpm 302
        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
187 mathias 303
        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
304
            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
305
        }
94 jpm 306
        // }}}
307
 
308
        if (empty($this->_options['register-only']) &&
309
              (!file_exists($dest_dir) || !is_dir($dest_dir))) {
310
            if (!$this->mkDirHier($dest_dir)) {
311
                return $this->raiseError("failed to mkdir $dest_dir",
312
                                         PEAR_INSTALLER_FAILED);
313
            }
314
            $this->log(3, "+ mkdir $dest_dir");
315
        }
187 mathias 316
 
94 jpm 317
        // pretty much nothing happens if we are only registering the install
318
        if (empty($this->_options['register-only'])) {
319
            if (empty($atts['replacements'])) {
320
                if (!file_exists($orig_file)) {
321
                    return $this->raiseError("file $orig_file does not exist",
322
                                             PEAR_INSTALLER_FAILED);
323
                }
187 mathias 324
 
94 jpm 325
                if (!@copy($orig_file, $dest_file)) {
326
                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
327
                                             PEAR_INSTALLER_FAILED);
328
                }
187 mathias 329
 
94 jpm 330
                $this->log(3, "+ cp $orig_file $dest_file");
331
                if (isset($atts['md5sum'])) {
332
                    $md5sum = md5_file($dest_file);
333
                }
334
            } else {
335
                // {{{ file with replacements
336
                if (!file_exists($orig_file)) {
337
                    return $this->raiseError("file does not exist",
338
                                             PEAR_INSTALLER_FAILED);
339
                }
187 mathias 340
 
94 jpm 341
                $contents = file_get_contents($orig_file);
342
                if ($contents === false) {
343
                    $contents = '';
344
                }
187 mathias 345
 
94 jpm 346
                if (isset($atts['md5sum'])) {
347
                    $md5sum = md5($contents);
348
                }
187 mathias 349
 
94 jpm 350
                $subst_from = $subst_to = array();
351
                foreach ($atts['replacements'] as $a) {
352
                    $to = '';
353
                    if ($a['type'] == 'php-const') {
187 mathias 354
                        if (preg_match('/^[a-z0-9_]+\\z/i', $a['to'])) {
94 jpm 355
                            eval("\$to = $a[to];");
356
                        } else {
357
                            if (!isset($options['soft'])) {
358
                                $this->log(0, "invalid php-const replacement: $a[to]");
359
                            }
360
                            continue;
361
                        }
362
                    } elseif ($a['type'] == 'pear-config') {
363
                        if ($a['to'] == 'master_server') {
364
                            $chan = $this->_registry->getChannel($channel);
365
                            if (!PEAR::isError($chan)) {
366
                                $to = $chan->getServer();
367
                            } else {
368
                                $to = $this->config->get($a['to'], null, $channel);
369
                            }
370
                        } else {
371
                            $to = $this->config->get($a['to'], null, $channel);
372
                        }
373
                        if (is_null($to)) {
374
                            if (!isset($options['soft'])) {
375
                                $this->log(0, "invalid pear-config replacement: $a[to]");
376
                            }
377
                            continue;
378
                        }
379
                    } elseif ($a['type'] == 'package-info') {
380
                        if ($t = $this->pkginfo->packageInfo($a['to'])) {
381
                            $to = $t;
382
                        } else {
383
                            if (!isset($options['soft'])) {
384
                                $this->log(0, "invalid package-info replacement: $a[to]");
385
                            }
386
                            continue;
387
                        }
388
                    }
389
                    if (!is_null($to)) {
390
                        $subst_from[] = $a['from'];
391
                        $subst_to[] = $to;
392
                    }
393
                }
187 mathias 394
 
94 jpm 395
                $this->log(3, "doing ".sizeof($subst_from)." substitution(s) for $final_dest_file");
396
                if (sizeof($subst_from)) {
397
                    $contents = str_replace($subst_from, $subst_to, $contents);
398
                }
187 mathias 399
 
94 jpm 400
                $wp = @fopen($dest_file, "wb");
401
                if (!is_resource($wp)) {
402
                    return $this->raiseError("failed to create $dest_file: $php_errormsg",
403
                                             PEAR_INSTALLER_FAILED);
404
                }
187 mathias 405
 
94 jpm 406
                if (@fwrite($wp, $contents) === false) {
407
                    return $this->raiseError("failed writing to $dest_file: $php_errormsg",
408
                                             PEAR_INSTALLER_FAILED);
409
                }
187 mathias 410
 
94 jpm 411
                fclose($wp);
412
                // }}}
413
            }
187 mathias 414
 
94 jpm 415
            // {{{ check the md5
416
            if (isset($md5sum)) {
187 mathias 417
                if (strtolower($md5sum) === strtolower($atts['md5sum'])) {
94 jpm 418
                    $this->log(2, "md5sum ok: $final_dest_file");
419
                } else {
420
                    if (empty($options['force'])) {
421
                        // delete the file
422
                        if (file_exists($dest_file)) {
423
                            unlink($dest_file);
424
                        }
187 mathias 425
 
94 jpm 426
                        if (!isset($options['ignore-errors'])) {
427
                            return $this->raiseError("bad md5sum for file $final_dest_file",
428
                                                 PEAR_INSTALLER_FAILED);
429
                        }
187 mathias 430
 
431
                        if (!isset($options['soft'])) {
432
                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
433
                        }
94 jpm 434
                    } else {
435
                        if (!isset($options['soft'])) {
436
                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
437
                        }
438
                    }
439
                }
440
            }
441
            // }}}
442
            // {{{ set file permissions
443
            if (!OS_WINDOWS) {
444
                if ($atts['role'] == 'script') {
445
                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
446
                    $this->log(3, "+ chmod +x $dest_file");
447
                } else {
448
                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
449
                }
187 mathias 450
 
451
                if ($atts['role'] != 'src') {
452
                    $this->addFileOperation("chmod", array($mode, $dest_file));
453
                    if (!@chmod($dest_file, $mode)) {
454
                        if (!isset($options['soft'])) {
455
                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
456
                        }
94 jpm 457
                    }
458
                }
459
            }
460
            // }}}
187 mathias 461
 
462
            if ($atts['role'] == 'src') {
463
                rename($dest_file, $final_dest_file);
464
                $this->log(2, "renamed source file $dest_file to $final_dest_file");
465
            } else {
466
                $this->addFileOperation("rename", array($dest_file, $final_dest_file,
467
                    $atts['role'] == 'ext'));
468
            }
94 jpm 469
        }
187 mathias 470
 
94 jpm 471
        // Store the full path where the file was installed for easy unistall
187 mathias 472
        if ($atts['role'] != 'script') {
473
            $loc = $this->config->get($atts['role'] . '_dir');
474
        } else {
475
            $loc = $this->config->get('bin_dir');
476
        }
94 jpm 477
 
187 mathias 478
        if ($atts['role'] != 'src') {
479
            $this->addFileOperation("installed_as", array($file, $installed_as,
480
                                    $loc,
481
                                    dirname(substr($installedas_dest_file, strlen($loc)))));
482
        }
483
 
94 jpm 484
        //$this->log(2, "installed: $dest_file");
485
        return PEAR_INSTALLER_OK;
486
    }
487
 
488
    // }}}
489
    // {{{ _installFile2()
490
 
491
    /**
492
     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
493
     * @param string filename
494
     * @param array attributes from <file> tag in package.xml
495
     * @param string path to install the file in
496
     * @param array options from command-line
497
     * @access private
498
     */
187 mathias 499
    function _installFile2(&$pkg, $file, &$real_atts, $tmp_path, $options)
94 jpm 500
    {
187 mathias 501
        $atts = $real_atts;
94 jpm 502
        if (!isset($this->_registry)) {
503
            $this->_registry = &$this->config->getRegistry();
504
        }
505
 
506
        $channel = $pkg->getChannel();
507
        // {{{ assemble the destination paths
508
        if (!in_array($atts['attribs']['role'],
509
              PEAR_Installer_Role::getValidRoles($pkg->getPackageType()))) {
510
            return $this->raiseError('Invalid role `' . $atts['attribs']['role'] .
511
                    "' for file $file");
512
        }
187 mathias 513
 
94 jpm 514
        $role = &PEAR_Installer_Role::factory($pkg, $atts['attribs']['role'], $this->config);
187 mathias 515
        $err  = $role->setup($this, $pkg, $atts['attribs'], $file);
94 jpm 516
        if (PEAR::isError($err)) {
517
            return $err;
518
        }
187 mathias 519
 
94 jpm 520
        if (!$role->isInstallable()) {
521
            return;
522
        }
187 mathias 523
 
94 jpm 524
        $info = $role->processInstallation($pkg, $atts['attribs'], $file, $tmp_path);
525
        if (PEAR::isError($info)) {
526
            return $info;
527
        }
187 mathias 528
 
529
        list($save_destdir, $dest_dir, $dest_file, $orig_file) = $info;
530
        if (preg_match('~/\.\.(/|\\z)|^\.\./~', str_replace('\\', '/', $dest_file))) {
531
            return $this->raiseError("SECURITY ERROR: file $file (installed to $dest_file) contains parent directory reference ..", PEAR_INSTALLER_FAILED);
532
        }
533
 
94 jpm 534
        $final_dest_file = $installed_as = $dest_file;
535
        if (isset($this->_options['packagingroot'])) {
536
            $final_dest_file = $this->_prependPath($final_dest_file,
537
                $this->_options['packagingroot']);
538
        }
187 mathias 539
 
540
        $dest_dir  = dirname($final_dest_file);
94 jpm 541
        $dest_file = $dest_dir . DIRECTORY_SEPARATOR . '.tmp' . basename($final_dest_file);
542
        // }}}
543
 
544
        if (empty($this->_options['register-only'])) {
545
            if (!file_exists($dest_dir) || !is_dir($dest_dir)) {
546
                if (!$this->mkDirHier($dest_dir)) {
547
                    return $this->raiseError("failed to mkdir $dest_dir",
548
                                             PEAR_INSTALLER_FAILED);
549
                }
550
                $this->log(3, "+ mkdir $dest_dir");
551
            }
552
        }
187 mathias 553
 
94 jpm 554
        $attribs = $atts['attribs'];
555
        unset($atts['attribs']);
556
        // pretty much nothing happens if we are only registering the install
557
        if (empty($this->_options['register-only'])) {
558
            if (!count($atts)) { // no tasks
559
                if (!file_exists($orig_file)) {
560
                    return $this->raiseError("file $orig_file does not exist",
561
                                             PEAR_INSTALLER_FAILED);
562
                }
187 mathias 563
 
94 jpm 564
                if (!@copy($orig_file, $dest_file)) {
565
                    return $this->raiseError("failed to write $dest_file: $php_errormsg",
566
                                             PEAR_INSTALLER_FAILED);
567
                }
187 mathias 568
 
94 jpm 569
                $this->log(3, "+ cp $orig_file $dest_file");
570
                if (isset($attribs['md5sum'])) {
571
                    $md5sum = md5_file($dest_file);
572
                }
573
            } else { // file with tasks
574
                if (!file_exists($orig_file)) {
575
                    return $this->raiseError("file $orig_file does not exist",
576
                                             PEAR_INSTALLER_FAILED);
577
                }
187 mathias 578
 
94 jpm 579
                $contents = file_get_contents($orig_file);
580
                if ($contents === false) {
581
                    $contents = '';
582
                }
187 mathias 583
 
94 jpm 584
                if (isset($attribs['md5sum'])) {
585
                    $md5sum = md5($contents);
586
                }
187 mathias 587
 
94 jpm 588
                foreach ($atts as $tag => $raw) {
187 mathias 589
                    $tag = str_replace(array($pkg->getTasksNs() . ':', '-'), array('', '_'), $tag);
94 jpm 590
                    $task = "PEAR_Task_$tag";
187 mathias 591
                    $task = new $task($this->config, $this, PEAR_TASK_INSTALL);
94 jpm 592
                    if (!$task->isScript()) { // scripts are only handled after installation
593
                        $task->init($raw, $attribs, $pkg->getLastInstalledVersion());
594
                        $res = $task->startSession($pkg, $contents, $final_dest_file);
595
                        if ($res === false) {
596
                            continue; // skip this file
597
                        }
187 mathias 598
 
94 jpm 599
                        if (PEAR::isError($res)) {
600
                            return $res;
601
                        }
187 mathias 602
 
94 jpm 603
                        $contents = $res; // save changes
604
                    }
187 mathias 605
 
94 jpm 606
                    $wp = @fopen($dest_file, "wb");
607
                    if (!is_resource($wp)) {
608
                        return $this->raiseError("failed to create $dest_file: $php_errormsg",
609
                                                 PEAR_INSTALLER_FAILED);
610
                    }
187 mathias 611
 
94 jpm 612
                    if (fwrite($wp, $contents) === false) {
613
                        return $this->raiseError("failed writing to $dest_file: $php_errormsg",
614
                                                 PEAR_INSTALLER_FAILED);
615
                    }
187 mathias 616
 
94 jpm 617
                    fclose($wp);
618
                }
619
            }
187 mathias 620
 
94 jpm 621
            // {{{ check the md5
622
            if (isset($md5sum)) {
187 mathias 623
                // Make sure the original md5 sum matches with expected
624
                if (strtolower($md5sum) === strtolower($attribs['md5sum'])) {
94 jpm 625
                    $this->log(2, "md5sum ok: $final_dest_file");
187 mathias 626
 
627
                    if (isset($contents)) {
628
                        // set md5 sum based on $content in case any tasks were run.
629
                        $real_atts['attribs']['md5sum'] = md5($contents);
630
                    }
94 jpm 631
                } else {
632
                    if (empty($options['force'])) {
633
                        // delete the file
634
                        if (file_exists($dest_file)) {
635
                            unlink($dest_file);
636
                        }
187 mathias 637
 
94 jpm 638
                        if (!isset($options['ignore-errors'])) {
639
                            return $this->raiseError("bad md5sum for file $final_dest_file",
640
                                                     PEAR_INSTALLER_FAILED);
641
                        }
187 mathias 642
 
643
                        if (!isset($options['soft'])) {
644
                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
645
                        }
94 jpm 646
                    } else {
647
                        if (!isset($options['soft'])) {
648
                            $this->log(0, "warning : bad md5sum for file $final_dest_file");
649
                        }
650
                    }
651
                }
187 mathias 652
            } else {
653
                $real_atts['attribs']['md5sum'] = md5_file($dest_file);
94 jpm 654
            }
187 mathias 655
 
94 jpm 656
            // }}}
657
            // {{{ set file permissions
658
            if (!OS_WINDOWS) {
659
                if ($role->isExecutable()) {
660
                    $mode = 0777 & ~(int)octdec($this->config->get('umask'));
661
                    $this->log(3, "+ chmod +x $dest_file");
662
                } else {
663
                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
664
                }
187 mathias 665
 
666
                if ($attribs['role'] != 'src') {
667
                    $this->addFileOperation("chmod", array($mode, $dest_file));
668
                    if (!@chmod($dest_file, $mode)) {
669
                        if (!isset($options['soft'])) {
670
                            $this->log(0, "failed to change mode of $dest_file: $php_errormsg");
671
                        }
94 jpm 672
                    }
673
                }
674
            }
675
            // }}}
187 mathias 676
 
677
            if ($attribs['role'] == 'src') {
678
                rename($dest_file, $final_dest_file);
679
                $this->log(2, "renamed source file $dest_file to $final_dest_file");
680
            } else {
681
                $this->addFileOperation("rename", array($dest_file, $final_dest_file, $role->isExtension()));
682
            }
94 jpm 683
        }
187 mathias 684
 
94 jpm 685
        // Store the full path where the file was installed for easy uninstall
187 mathias 686
        if ($attribs['role'] != 'src') {
687
            $loc = $this->config->get($role->getLocationConfig(), null, $channel);
688
            $this->addFileOperation('installed_as', array($file, $installed_as,
689
                                $loc,
690
                                dirname(substr($installed_as, strlen($loc)))));
691
        }
94 jpm 692
 
693
        //$this->log(2, "installed: $dest_file");
694
        return PEAR_INSTALLER_OK;
695
    }
696
 
697
    // }}}
698
    // {{{ addFileOperation()
699
 
700
    /**
701
     * Add a file operation to the current file transaction.
702
     *
703
     * @see startFileTransaction()
704
     * @param string $type This can be one of:
705
     *    - rename:  rename a file ($data has 3 values)
706
     *    - backup:  backup an existing file ($data has 1 value)
707
     *    - removebackup:  clean up backups created during install ($data has 1 value)
708
     *    - chmod:   change permissions on a file ($data has 2 values)
709
     *    - delete:  delete a file ($data has 1 value)
710
     *    - rmdir:   delete a directory if empty ($data has 1 value)
711
     *    - installed_as: mark a file as installed ($data has 4 values).
712
     * @param array $data For all file operations, this array must contain the
713
     *    full path to the file or directory that is being operated on.  For
714
     *    the rename command, the first parameter must be the file to rename,
715
     *    the second its new name, the third whether this is a PHP extension.
716
     *
717
     *    The installed_as operation contains 4 elements in this order:
718
     *    1. Filename as listed in the filelist element from package.xml
719
     *    2. Full path to the installed file
720
     *    3. Full path from the php_dir configuration variable used in this
721
     *       installation
722
     *    4. Relative path from the php_dir that this file is installed in
723
     */
724
    function addFileOperation($type, $data)
725
    {
726
        if (!is_array($data)) {
727
            return $this->raiseError('Internal Error: $data in addFileOperation'
728
                . ' must be an array, was ' . gettype($data));
729
        }
187 mathias 730
 
94 jpm 731
        if ($type == 'chmod') {
732
            $octmode = decoct($data[0]);
733
            $this->log(3, "adding to transaction: $type $octmode $data[1]");
734
        } else {
735
            $this->log(3, "adding to transaction: $type " . implode(" ", $data));
736
        }
737
        $this->file_operations[] = array($type, $data);
738
    }
739
 
740
    // }}}
741
    // {{{ startFileTransaction()
742
 
743
    function startFileTransaction($rollback_in_case = false)
744
    {
745
        if (count($this->file_operations) && $rollback_in_case) {
746
            $this->rollbackFileTransaction();
747
        }
748
        $this->file_operations = array();
749
    }
750
 
751
    // }}}
752
    // {{{ commitFileTransaction()
753
 
754
    function commitFileTransaction()
755
    {
756
        // {{{ first, check permissions and such manually
757
        $errors = array();
187 mathias 758
        foreach ($this->file_operations as $key => $tr) {
94 jpm 759
            list($type, $data) = $tr;
760
            switch ($type) {
761
                case 'rename':
762
                    if (!file_exists($data[0])) {
763
                        $errors[] = "cannot rename file $data[0], doesn't exist";
764
                    }
187 mathias 765
 
94 jpm 766
                    // check that dest dir. is writable
767
                    if (!is_writable(dirname($data[1]))) {
768
                        $errors[] = "permission denied ($type): $data[1]";
769
                    }
770
                    break;
771
                case 'chmod':
772
                    // check that file is writable
773
                    if (!is_writable($data[1])) {
774
                        $errors[] = "permission denied ($type): $data[1] " . decoct($data[0]);
775
                    }
776
                    break;
777
                case 'delete':
778
                    if (!file_exists($data[0])) {
779
                        $this->log(2, "warning: file $data[0] doesn't exist, can't be deleted");
780
                    }
781
                    // check that directory is writable
782
                    if (file_exists($data[0])) {
783
                        if (!is_writable(dirname($data[0]))) {
784
                            $errors[] = "permission denied ($type): $data[0]";
785
                        } else {
786
                            // make sure the file to be deleted can be opened for writing
787
                            $fp = false;
788
                            if (!is_dir($data[0]) &&
789
                                  (!is_writable($data[0]) || !($fp = @fopen($data[0], 'a')))) {
790
                                $errors[] = "permission denied ($type): $data[0]";
791
                            } elseif ($fp) {
792
                                fclose($fp);
793
                            }
794
                        }
187 mathias 795
 
796
                        /* Verify we are not deleting a file owned by another package
797
                         * This can happen when a file moves from package A to B in
798
                         * an upgrade ala http://pear.php.net/17986
799
                         */
800
                        $info = array(
801
                            'package' => strtolower($this->pkginfo->getName()),
802
                            'channel' => strtolower($this->pkginfo->getChannel()),
803
                        );
804
                        $result = $this->_registry->checkFileMap($data[0], $info, '1.1');
805
                        if (is_array($result)) {
806
                            $res = array_diff($result, $info);
807
                            if (!empty($res)) {
808
                                $new = $this->_registry->getPackage($result[1], $result[0]);
809
                                $this->file_operations[$key] = false;
810
                                $pkginfoName = $this->pkginfo->getName();
811
                                $newChannel  = $new->getChannel();
812
                                $newPackage  = $new->getName();
813
                                $this->log(3, "file $data[0] was scheduled for removal from $pkginfoName but is owned by $newChannel/$newPackage, removal has been cancelled.");
814
                            }
815
                        }
94 jpm 816
                    }
817
                    break;
818
            }
819
 
820
        }
821
        // }}}
187 mathias 822
 
823
        $n = count($this->file_operations);
824
        $this->log(2, "about to commit $n file operations for " . $this->pkginfo->getName());
825
 
826
        $m = count($errors);
94 jpm 827
        if ($m > 0) {
828
            foreach ($errors as $error) {
829
                if (!isset($this->_options['soft'])) {
830
                    $this->log(1, $error);
831
                }
832
            }
187 mathias 833
 
94 jpm 834
            if (!isset($this->_options['ignore-errors'])) {
835
                return false;
836
            }
837
        }
187 mathias 838
 
94 jpm 839
        $this->_dirtree = array();
840
        // {{{ really commit the transaction
841
        foreach ($this->file_operations as $i => $tr) {
842
            if (!$tr) {
843
                // support removal of non-existing backups
844
                continue;
845
            }
187 mathias 846
 
94 jpm 847
            list($type, $data) = $tr;
848
            switch ($type) {
849
                case 'backup':
850
                    if (!file_exists($data[0])) {
851
                        $this->file_operations[$i] = false;
852
                        break;
853
                    }
187 mathias 854
 
94 jpm 855
                    if (!@copy($data[0], $data[0] . '.bak')) {
856
                        $this->log(1, 'Could not copy ' . $data[0] . ' to ' . $data[0] .
857
                            '.bak ' . $php_errormsg);
858
                        return false;
859
                    }
860
                    $this->log(3, "+ backup $data[0] to $data[0].bak");
861
                    break;
862
                case 'removebackup':
863
                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
864
                        unlink($data[0] . '.bak');
865
                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
866
                    }
867
                    break;
868
                case 'rename':
187 mathias 869
                    $test = file_exists($data[1]) ? @unlink($data[1]) : null;
94 jpm 870
                    if (!$test && file_exists($data[1])) {
871
                        if ($data[2]) {
872
                            $extra = ', this extension must be installed manually.  Rename to "' .
873
                                basename($data[1]) . '"';
874
                        } else {
875
                            $extra = '';
876
                        }
187 mathias 877
 
94 jpm 878
                        if (!isset($this->_options['soft'])) {
879
                            $this->log(1, 'Could not delete ' . $data[1] . ', cannot rename ' .
880
                                $data[0] . $extra);
881
                        }
187 mathias 882
 
94 jpm 883
                        if (!isset($this->_options['ignore-errors'])) {
884
                            return false;
885
                        }
886
                    }
187 mathias 887
 
94 jpm 888
                    // permissions issues with rename - copy() is far superior
889
                    $perms = @fileperms($data[0]);
890
                    if (!@copy($data[0], $data[1])) {
891
                        $this->log(1, 'Could not rename ' . $data[0] . ' to ' . $data[1] .
892
                            ' ' . $php_errormsg);
893
                        return false;
894
                    }
187 mathias 895
 
94 jpm 896
                    // copy over permissions, otherwise they are lost
897
                    @chmod($data[1], $perms);
898
                    @unlink($data[0]);
899
                    $this->log(3, "+ mv $data[0] $data[1]");
900
                    break;
901
                case 'chmod':
902
                    if (!@chmod($data[1], $data[0])) {
903
                        $this->log(1, 'Could not chmod ' . $data[1] . ' to ' .
904
                            decoct($data[0]) . ' ' . $php_errormsg);
905
                        return false;
906
                    }
187 mathias 907
 
94 jpm 908
                    $octmode = decoct($data[0]);
909
                    $this->log(3, "+ chmod $octmode $data[1]");
910
                    break;
911
                case 'delete':
912
                    if (file_exists($data[0])) {
913
                        if (!@unlink($data[0])) {
914
                            $this->log(1, 'Could not delete ' . $data[0] . ' ' .
915
                                $php_errormsg);
916
                            return false;
917
                        }
918
                        $this->log(3, "+ rm $data[0]");
919
                    }
920
                    break;
921
                case 'rmdir':
922
                    if (file_exists($data[0])) {
923
                        do {
924
                            $testme = opendir($data[0]);
925
                            while (false !== ($entry = readdir($testme))) {
926
                                if ($entry == '.' || $entry == '..') {
927
                                    continue;
928
                                }
929
                                closedir($testme);
930
                                break 2; // this directory is not empty and can't be
931
                                         // deleted
932
                            }
187 mathias 933
 
94 jpm 934
                            closedir($testme);
935
                            if (!@rmdir($data[0])) {
936
                                $this->log(1, 'Could not rmdir ' . $data[0] . ' ' .
937
                                    $php_errormsg);
938
                                return false;
939
                            }
940
                            $this->log(3, "+ rmdir $data[0]");
941
                        } while (false);
942
                    }
943
                    break;
944
                case 'installed_as':
945
                    $this->pkginfo->setInstalledAs($data[0], $data[1]);
946
                    if (!isset($this->_dirtree[dirname($data[1])])) {
947
                        $this->_dirtree[dirname($data[1])] = true;
948
                        $this->pkginfo->setDirtree(dirname($data[1]));
949
 
187 mathias 950
                        while(!empty($data[3]) && dirname($data[3]) != $data[3] &&
951
                                $data[3] != '/' && $data[3] != '\\') {
94 jpm 952
                            $this->pkginfo->setDirtree($pp =
953
                                $this->_prependPath($data[3], $data[2]));
954
                            $this->_dirtree[$pp] = true;
955
                            $data[3] = dirname($data[3]);
956
                        }
957
                    }
958
                    break;
959
            }
960
        }
961
        // }}}
962
        $this->log(2, "successfully committed $n file operations");
963
        $this->file_operations = array();
964
        return true;
965
    }
966
 
967
    // }}}
968
    // {{{ rollbackFileTransaction()
969
 
970
    function rollbackFileTransaction()
971
    {
972
        $n = count($this->file_operations);
973
        $this->log(2, "rolling back $n file operations");
974
        foreach ($this->file_operations as $tr) {
975
            list($type, $data) = $tr;
976
            switch ($type) {
977
                case 'backup':
978
                    if (file_exists($data[0] . '.bak')) {
979
                        if (file_exists($data[0] && is_writable($data[0]))) {
980
                            unlink($data[0]);
981
                        }
982
                        @copy($data[0] . '.bak', $data[0]);
983
                        $this->log(3, "+ restore $data[0] from $data[0].bak");
984
                    }
985
                    break;
986
                case 'removebackup':
987
                    if (file_exists($data[0] . '.bak') && is_writable($data[0] . '.bak')) {
988
                        unlink($data[0] . '.bak');
989
                        $this->log(3, "+ rm backup of $data[0] ($data[0].bak)");
990
                    }
991
                    break;
992
                case 'rename':
993
                    @unlink($data[0]);
994
                    $this->log(3, "+ rm $data[0]");
995
                    break;
996
                case 'mkdir':
997
                    @rmdir($data[0]);
998
                    $this->log(3, "+ rmdir $data[0]");
999
                    break;
1000
                case 'chmod':
1001
                    break;
1002
                case 'delete':
1003
                    break;
1004
                case 'installed_as':
1005
                    $this->pkginfo->setInstalledAs($data[0], false);
1006
                    break;
1007
            }
1008
        }
1009
        $this->pkginfo->resetDirtree();
1010
        $this->file_operations = array();
1011
    }
1012
 
1013
    // }}}
1014
    // {{{ mkDirHier($dir)
1015
 
1016
    function mkDirHier($dir)
1017
    {
1018
        $this->addFileOperation('mkdir', array($dir));
1019
        return parent::mkDirHier($dir);
1020
    }
1021
 
1022
    // }}}
1023
    // {{{ _parsePackageXml()
1024
 
187 mathias 1025
    function _parsePackageXml(&$descfile)
94 jpm 1026
    {
1027
        // Parse xml file -----------------------------------------------
187 mathias 1028
        $pkg = new PEAR_PackageFile($this->config, $this->debug);
94 jpm 1029
        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1030
        $p = &$pkg->fromAnyFile($descfile, PEAR_VALIDATE_INSTALLING);
1031
        PEAR::staticPopErrorHandling();
1032
        if (PEAR::isError($p)) {
1033
            if (is_array($p->getUserInfo())) {
1034
                foreach ($p->getUserInfo() as $err) {
1035
                    $loglevel = $err['level'] == 'error' ? 0 : 1;
1036
                    if (!isset($this->_options['soft'])) {
1037
                        $this->log($loglevel, ucfirst($err['level']) . ': ' . $err['message']);
1038
                    }
1039
                }
1040
            }
1041
            return $this->raiseError('Installation failed: invalid package file');
1042
        }
187 mathias 1043
 
1044
        $descfile = $p->getPackageFile();
94 jpm 1045
        return $p;
1046
    }
1047
 
1048
    // }}}
1049
    /**
1050
     * Set the list of PEAR_Downloader_Package objects to allow more sane
1051
     * dependency validation
1052
     * @param array
1053
     */
1054
    function setDownloadedPackages(&$pkgs)
1055
    {
1056
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1057
        $err = $this->analyzeDependencies($pkgs);
1058
        PEAR::popErrorHandling();
1059
        if (PEAR::isError($err)) {
1060
            return $err;
1061
        }
1062
        $this->_downloadedPackages = &$pkgs;
1063
    }
1064
 
1065
    /**
1066
     * Set the list of PEAR_Downloader_Package objects to allow more sane
1067
     * dependency validation
1068
     * @param array
1069
     */
1070
    function setUninstallPackages(&$pkgs)
1071
    {
1072
        $this->_downloadedPackages = &$pkgs;
1073
    }
1074
 
1075
    function getInstallPackages()
1076
    {
1077
        return $this->_downloadedPackages;
1078
    }
1079
 
1080
    // {{{ install()
1081
 
1082
    /**
1083
     * Installs the files within the package file specified.
1084
     *
1085
     * @param string|PEAR_Downloader_Package $pkgfile path to the package file,
1086
     *        or a pre-initialized packagefile object
1087
     * @param array $options
1088
     * recognized options:
1089
     * - installroot   : optional prefix directory for installation
1090
     * - force         : force installation
1091
     * - register-only : update registry but don't install files
1092
     * - upgrade       : upgrade existing install
1093
     * - soft          : fail silently
1094
     * - nodeps        : ignore dependency conflicts/missing dependencies
1095
     * - alldeps       : install all dependencies
1096
     * - onlyreqdeps   : install only required dependencies
1097
     *
1098
     * @return array|PEAR_Error package info if successful
1099
     */
1100
    function install($pkgfile, $options = array())
1101
    {
1102
        $this->_options = $options;
1103
        $this->_registry = &$this->config->getRegistry();
1104
        if (is_object($pkgfile)) {
187 mathias 1105
            $dlpkg    = &$pkgfile;
1106
            $pkg      = $pkgfile->getPackageFile();
1107
            $pkgfile  = $pkg->getArchiveFile();
94 jpm 1108
            $descfile = $pkg->getPackageFile();
1109
        } else {
1110
            $descfile = $pkgfile;
187 mathias 1111
            $pkg      = $this->_parsePackageXml($descfile);
1112
            if (PEAR::isError($pkg)) {
94 jpm 1113
                return $pkg;
1114
            }
1115
        }
1116
 
187 mathias 1117
        $tmpdir = dirname($descfile);
94 jpm 1118
        if (realpath($descfile) != realpath($pkgfile)) {
187 mathias 1119
            // Use the temp_dir since $descfile can contain the download dir path
1120
            $tmpdir = $this->config->get('temp_dir', null, 'pear.php.net');
1121
            $tmpdir = System::mktemp('-d -t "' . $tmpdir . '"');
1122
 
94 jpm 1123
            $tar = new Archive_Tar($pkgfile);
1124
            if (!$tar->extract($tmpdir)) {
1125
                return $this->raiseError("unable to unpack $pkgfile");
1126
            }
1127
        }
1128
 
1129
        $pkgname = $pkg->getName();
1130
        $channel = $pkg->getChannel();
1131
 
1132
        if (isset($options['installroot'])) {
1133
            $this->config->setInstallRoot($options['installroot']);
1134
            $this->_registry = &$this->config->getRegistry();
1135
            $installregistry = &$this->_registry;
1136
            $this->installroot = ''; // all done automagically now
1137
            $php_dir = $this->config->get('php_dir', null, $channel);
1138
        } else {
1139
            $this->config->setInstallRoot(false);
1140
            $this->_registry = &$this->config->getRegistry();
1141
            if (isset($this->_options['packagingroot'])) {
187 mathias 1142
                $regdir = $this->_prependPath(
1143
                    $this->config->get('php_dir', null, 'pear.php.net'),
1144
                    $this->_options['packagingroot']);
1145
 
1146
                $metadata_dir = $this->config->get('metadata_dir', null, 'pear.php.net');
1147
                if ($metadata_dir) {
1148
                    $metadata_dir = $this->_prependPath(
1149
                        $metadata_dir,
1150
                        $this->_options['packagingroot']);
1151
                }
1152
                $packrootphp_dir = $this->_prependPath(
1153
                    $this->config->get('php_dir', null, $channel),
1154
                    $this->_options['packagingroot']);
1155
 
1156
                $installregistry = new PEAR_Registry($regdir, false, false, $metadata_dir);
94 jpm 1157
                if (!$installregistry->channelExists($channel, true)) {
1158
                    // we need to fake a channel-discover of this channel
1159
                    $chanobj = $this->_registry->getChannel($channel, true);
1160
                    $installregistry->addChannel($chanobj);
1161
                }
1162
                $php_dir = $packrootphp_dir;
1163
            } else {
1164
                $installregistry = &$this->_registry;
1165
                $php_dir = $this->config->get('php_dir', null, $channel);
1166
            }
1167
            $this->installroot = '';
1168
        }
1169
 
1170
        // {{{ checks to do when not in "force" mode
1171
        if (empty($options['force']) &&
1172
              (file_exists($this->config->get('php_dir')) &&
1173
               is_dir($this->config->get('php_dir')))) {
1174
            $testp = $channel == 'pear.php.net' ? $pkgname : array($channel, $pkgname);
1175
            $instfilelist = $pkg->getInstallationFileList(true);
1176
            if (PEAR::isError($instfilelist)) {
1177
                return $instfilelist;
1178
            }
187 mathias 1179
 
94 jpm 1180
            // ensure we have the most accurate registry
1181
            $installregistry->flushFileMap();
1182
            $test = $installregistry->checkFileMap($instfilelist, $testp, '1.1');
1183
            if (PEAR::isError($test)) {
1184
                return $test;
1185
            }
187 mathias 1186
 
94 jpm 1187
            if (sizeof($test)) {
1188
                $pkgs = $this->getInstallPackages();
1189
                $found = false;
1190
                foreach ($pkgs as $param) {
1191
                    if ($pkg->isSubpackageOf($param)) {
1192
                        $found = true;
1193
                        break;
1194
                    }
1195
                }
187 mathias 1196
 
94 jpm 1197
                if ($found) {
1198
                    // subpackages can conflict with earlier versions of parent packages
1199
                    $parentreg = $installregistry->packageInfo($param->getPackage(), null, $param->getChannel());
1200
                    $tmp = $test;
1201
                    foreach ($tmp as $file => $info) {
1202
                        if (is_array($info)) {
1203
                            if (strtolower($info[1]) == strtolower($param->getPackage()) &&
187 mathias 1204
                                  strtolower($info[0]) == strtolower($param->getChannel())
1205
                            ) {
1206
                                if (isset($parentreg['filelist'][$file])) {
1207
                                    unset($parentreg['filelist'][$file]);
1208
                                } else{
1209
                                    $pos     = strpos($file, '/');
1210
                                    $basedir = substr($file, 0, $pos);
1211
                                    $file2   = substr($file, $pos + 1);
1212
                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1213
                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1214
                                    ) {
1215
                                        unset($parentreg['filelist'][$file2]);
1216
                                    }
1217
                                }
1218
 
94 jpm 1219
                                unset($test[$file]);
1220
                            }
1221
                        } else {
1222
                            if (strtolower($param->getChannel()) != 'pear.php.net') {
1223
                                continue;
1224
                            }
187 mathias 1225
 
94 jpm 1226
                            if (strtolower($info) == strtolower($param->getPackage())) {
187 mathias 1227
                                if (isset($parentreg['filelist'][$file])) {
1228
                                    unset($parentreg['filelist'][$file]);
1229
                                } else{
1230
                                    $pos     = strpos($file, '/');
1231
                                    $basedir = substr($file, 0, $pos);
1232
                                    $file2   = substr($file, $pos + 1);
1233
                                    if (isset($parentreg['filelist'][$file2]['baseinstalldir'])
1234
                                        && $parentreg['filelist'][$file2]['baseinstalldir'] === $basedir
1235
                                    ) {
1236
                                        unset($parentreg['filelist'][$file2]);
1237
                                    }
1238
                                }
1239
 
94 jpm 1240
                                unset($test[$file]);
1241
                            }
1242
                        }
1243
                    }
187 mathias 1244
 
1245
                    $pfk = new PEAR_PackageFile($this->config);
94 jpm 1246
                    $parentpkg = &$pfk->fromArray($parentreg);
1247
                    $installregistry->updatePackage2($parentpkg);
1248
                }
187 mathias 1249
 
94 jpm 1250
                if ($param->getChannel() == 'pecl.php.net' && isset($options['upgrade'])) {
1251
                    $tmp = $test;
1252
                    foreach ($tmp as $file => $info) {
1253
                        if (is_string($info)) {
1254
                            // pear.php.net packages are always stored as strings
1255
                            if (strtolower($info) == strtolower($param->getPackage())) {
1256
                                // upgrading existing package
1257
                                unset($test[$file]);
1258
                            }
1259
                        }
1260
                    }
1261
                }
187 mathias 1262
 
1263
                if (count($test)) {
94 jpm 1264
                    $msg = "$channel/$pkgname: conflicting files found:\n";
1265
                    $longest = max(array_map("strlen", array_keys($test)));
1266
                    $fmt = "%${longest}s (%s)\n";
1267
                    foreach ($test as $file => $info) {
1268
                        if (!is_array($info)) {
1269
                            $info = array('pear.php.net', $info);
1270
                        }
1271
                        $info = $info[0] . '/' . $info[1];
1272
                        $msg .= sprintf($fmt, $file, $info);
1273
                    }
187 mathias 1274
 
94 jpm 1275
                    if (!isset($options['ignore-errors'])) {
1276
                        return $this->raiseError($msg);
1277
                    }
187 mathias 1278
 
1279
                    if (!isset($options['soft'])) {
1280
                        $this->log(0, "WARNING: $msg");
1281
                    }
94 jpm 1282
                }
1283
            }
1284
        }
1285
        // }}}
1286
 
1287
        $this->startFileTransaction();
1288
 
187 mathias 1289
        $usechannel = $channel;
1290
        if ($channel == 'pecl.php.net') {
1291
            $test = $installregistry->packageExists($pkgname, $channel);
1292
            if (!$test) {
1293
                $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1294
                $usechannel = 'pear.php.net';
1295
            }
1296
        } else {
1297
            $test = $installregistry->packageExists($pkgname, $channel);
1298
        }
1299
 
94 jpm 1300
        if (empty($options['upgrade']) && empty($options['soft'])) {
1301
            // checks to do only when installing new packages
1302
            if (empty($options['force']) && $test) {
1303
                return $this->raiseError("$channel/$pkgname is already installed");
1304
            }
1305
        } else {
187 mathias 1306
            // Upgrade
94 jpm 1307
            if ($test) {
1308
                $v1 = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1309
                $v2 = $pkg->getVersion();
1310
                $cmp = version_compare("$v1", "$v2", 'gt');
1311
                if (empty($options['force']) && !version_compare("$v2", "$v1", 'gt')) {
1312
                    return $this->raiseError("upgrade to a newer version ($v2 is not newer than $v1)");
1313
                }
187 mathias 1314
            }
1315
        }
1316
 
1317
        // Do cleanups for upgrade and install, remove old release's files first
1318
        if ($test && empty($options['register-only'])) {
1319
            // when upgrading, remove old release's files first:
1320
            if (PEAR::isError($err = $this->_deletePackageFiles($pkgname, $usechannel,
1321
                  true))) {
1322
                if (!isset($options['ignore-errors'])) {
1323
                    return $this->raiseError($err);
94 jpm 1324
                }
187 mathias 1325
 
1326
                if (!isset($options['soft'])) {
1327
                    $this->log(0, 'WARNING: ' . $err->getMessage());
1328
                }
1329
            } else {
1330
                $backedup = $err;
94 jpm 1331
            }
1332
        }
1333
 
1334
        // {{{ Copy files to dest dir ---------------------------------------
1335
 
1336
        // info from the package it self we want to access from _installFile
1337
        $this->pkginfo = &$pkg;
1338
        // used to determine whether we should build any C code
1339
        $this->source_files = 0;
1340
 
1341
        $savechannel = $this->config->get('default_channel');
1342
        if (empty($options['register-only']) && !is_dir($php_dir)) {
1343
            if (PEAR::isError(System::mkdir(array('-p'), $php_dir))) {
1344
                return $this->raiseError("no installation destination directory '$php_dir'\n");
1345
            }
1346
        }
1347
 
1348
        if (substr($pkgfile, -4) != '.xml') {
187 mathias 1349
            $tmpdir .= DIRECTORY_SEPARATOR . $pkgname . '-' . $pkg->getVersion();
94 jpm 1350
        }
1351
 
1352
        $this->configSet('default_channel', $channel);
1353
        // {{{ install files
1354
 
1355
        $ver = $pkg->getPackagexmlVersion();
1356
        if (version_compare($ver, '2.0', '>=')) {
1357
            $filelist = $pkg->getInstallationFilelist();
1358
        } else {
1359
            $filelist = $pkg->getFileList();
1360
        }
187 mathias 1361
 
94 jpm 1362
        if (PEAR::isError($filelist)) {
1363
            return $filelist;
1364
        }
187 mathias 1365
 
1366
        $p = &$installregistry->getPackage($pkgname, $channel);
1367
        $dirtree = (empty($options['register-only']) && $p) ? $p->getDirTree() : false;
1368
 
94 jpm 1369
        $pkg->resetFilelist();
1370
        $pkg->setLastInstalledVersion($installregistry->packageInfo($pkg->getPackage(),
1371
            'version', $pkg->getChannel()));
1372
        foreach ($filelist as $file => $atts) {
187 mathias 1373
            $this->expectError(PEAR_INSTALLER_FAILED);
94 jpm 1374
            if ($pkg->getPackagexmlVersion() == '1.0') {
187 mathias 1375
                $res = $this->_installFile($file, $atts, $tmpdir, $options);
94 jpm 1376
            } else {
187 mathias 1377
                $res = $this->_installFile2($pkg, $file, $atts, $tmpdir, $options);
94 jpm 1378
            }
187 mathias 1379
            $this->popExpect();
1380
 
94 jpm 1381
            if (PEAR::isError($res)) {
1382
                if (empty($options['ignore-errors'])) {
1383
                    $this->rollbackFileTransaction();
1384
                    if ($res->getMessage() == "file does not exist") {
1385
                        $this->raiseError("file $file in package.xml does not exist");
1386
                    }
187 mathias 1387
 
94 jpm 1388
                    return $this->raiseError($res);
1389
                }
187 mathias 1390
 
1391
                if (!isset($options['soft'])) {
1392
                    $this->log(0, "Warning: " . $res->getMessage());
1393
                }
94 jpm 1394
            }
187 mathias 1395
 
1396
            $real = isset($atts['attribs']) ? $atts['attribs'] : $atts;
1397
            if ($res == PEAR_INSTALLER_OK && $real['role'] != 'src') {
94 jpm 1398
                // Register files that were installed
1399
                $pkg->installedFile($file, $atts);
1400
            }
1401
        }
1402
        // }}}
1403
 
1404
        // {{{ compile and install source files
1405
        if ($this->source_files > 0 && empty($options['nobuild'])) {
1406
            if (PEAR::isError($err =
1407
                  $this->_compileSourceFiles($savechannel, $pkg))) {
1408
                return $err;
1409
            }
1410
        }
1411
        // }}}
1412
 
1413
        if (isset($backedup)) {
1414
            $this->_removeBackups($backedup);
1415
        }
187 mathias 1416
 
94 jpm 1417
        if (!$this->commitFileTransaction()) {
1418
            $this->rollbackFileTransaction();
1419
            $this->configSet('default_channel', $savechannel);
1420
            return $this->raiseError("commit failed", PEAR_INSTALLER_FAILED);
1421
        }
1422
        // }}}
1423
 
187 mathias 1424
        $ret          = false;
94 jpm 1425
        $installphase = 'install';
187 mathias 1426
        $oldversion   = false;
94 jpm 1427
        // {{{ Register that the package is installed -----------------------
1428
        if (empty($options['upgrade'])) {
1429
            // if 'force' is used, replace the info in registry
1430
            $usechannel = $channel;
1431
            if ($channel == 'pecl.php.net') {
1432
                $test = $installregistry->packageExists($pkgname, $channel);
1433
                if (!$test) {
1434
                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1435
                    $usechannel = 'pear.php.net';
1436
                }
1437
            } else {
1438
                $test = $installregistry->packageExists($pkgname, $channel);
1439
            }
187 mathias 1440
 
94 jpm 1441
            if (!empty($options['force']) && $test) {
1442
                $oldversion = $installregistry->packageInfo($pkgname, 'version', $usechannel);
1443
                $installregistry->deletePackage($pkgname, $usechannel);
1444
            }
1445
            $ret = $installregistry->addPackage2($pkg);
1446
        } else {
187 mathias 1447
            if ($dirtree) {
1448
                $this->startFileTransaction();
1449
                // attempt to delete empty directories
1450
                uksort($dirtree, array($this, '_sortDirs'));
1451
                foreach($dirtree as $dir => $notused) {
1452
                    $this->addFileOperation('rmdir', array($dir));
1453
                }
1454
                $this->commitFileTransaction();
1455
            }
1456
 
94 jpm 1457
            $usechannel = $channel;
1458
            if ($channel == 'pecl.php.net') {
1459
                $test = $installregistry->packageExists($pkgname, $channel);
1460
                if (!$test) {
1461
                    $test = $installregistry->packageExists($pkgname, 'pear.php.net');
1462
                    $usechannel = 'pear.php.net';
1463
                }
1464
            } else {
1465
                $test = $installregistry->packageExists($pkgname, $channel);
1466
            }
187 mathias 1467
 
94 jpm 1468
            // new: upgrade installs a package if it isn't installed
1469
            if (!$test) {
1470
                $ret = $installregistry->addPackage2($pkg);
1471
            } else {
1472
                if ($usechannel != $channel) {
1473
                    $installregistry->deletePackage($pkgname, $usechannel);
1474
                    $ret = $installregistry->addPackage2($pkg);
1475
                } else {
1476
                    $ret = $installregistry->updatePackage2($pkg);
1477
                }
1478
                $installphase = 'upgrade';
1479
            }
1480
        }
187 mathias 1481
 
94 jpm 1482
        if (!$ret) {
1483
            $this->configSet('default_channel', $savechannel);
1484
            return $this->raiseError("Adding package $channel/$pkgname to registry failed");
1485
        }
1486
        // }}}
187 mathias 1487
 
94 jpm 1488
        $this->configSet('default_channel', $savechannel);
1489
        if (class_exists('PEAR_Task_Common')) { // this is auto-included if any tasks exist
1490
            if (PEAR_Task_Common::hasPostinstallTasks()) {
1491
                PEAR_Task_Common::runPostinstallTasks($installphase);
1492
            }
1493
        }
187 mathias 1494
 
94 jpm 1495
        return $pkg->toArray(true);
1496
    }
1497
 
1498
    // }}}
1499
 
1500
    // {{{ _compileSourceFiles()
1501
    /**
1502
     * @param string
1503
     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
1504
     */
1505
    function _compileSourceFiles($savechannel, &$filelist)
1506
    {
1507
        require_once 'PEAR/Builder.php';
1508
        $this->log(1, "$this->source_files source files, building");
187 mathias 1509
        $bob = new PEAR_Builder($this->ui);
94 jpm 1510
        $bob->debug = $this->debug;
1511
        $built = $bob->build($filelist, array(&$this, '_buildCallback'));
1512
        if (PEAR::isError($built)) {
1513
            $this->rollbackFileTransaction();
1514
            $this->configSet('default_channel', $savechannel);
1515
            return $built;
1516
        }
187 mathias 1517
 
94 jpm 1518
        $this->log(1, "\nBuild process completed successfully");
1519
        foreach ($built as $ext) {
1520
            $bn = basename($ext['file']);
1521
            list($_ext_name, $_ext_suff) = explode('.', $bn);
1522
            if ($_ext_suff == '.so' || $_ext_suff == '.dll') {
1523
                if (extension_loaded($_ext_name)) {
1524
                    $this->raiseError("Extension '$_ext_name' already loaded. " .
1525
                                      'Please unload it in your php.ini file ' .
1526
                                      'prior to install or upgrade');
1527
                }
1528
                $role = 'ext';
1529
            } else {
1530
                $role = 'src';
1531
            }
187 mathias 1532
 
94 jpm 1533
            $dest = $ext['dest'];
1534
            $packagingroot = '';
1535
            if (isset($this->_options['packagingroot'])) {
1536
                $packagingroot = $this->_options['packagingroot'];
1537
            }
187 mathias 1538
 
94 jpm 1539
            $copyto = $this->_prependPath($dest, $packagingroot);
187 mathias 1540
            $extra  = $copyto != $dest ? " as '$copyto'" : '';
1541
            $this->log(1, "Installing '$dest'$extra");
1542
 
94 jpm 1543
            $copydir = dirname($copyto);
1544
            // pretty much nothing happens if we are only registering the install
1545
            if (empty($this->_options['register-only'])) {
1546
                if (!file_exists($copydir) || !is_dir($copydir)) {
1547
                    if (!$this->mkDirHier($copydir)) {
1548
                        return $this->raiseError("failed to mkdir $copydir",
1549
                            PEAR_INSTALLER_FAILED);
1550
                    }
187 mathias 1551
 
94 jpm 1552
                    $this->log(3, "+ mkdir $copydir");
1553
                }
187 mathias 1554
 
94 jpm 1555
                if (!@copy($ext['file'], $copyto)) {
1556
                    return $this->raiseError("failed to write $copyto ($php_errormsg)", PEAR_INSTALLER_FAILED);
1557
                }
187 mathias 1558
 
94 jpm 1559
                $this->log(3, "+ cp $ext[file] $copyto");
1560
                $this->addFileOperation('rename', array($ext['file'], $copyto));
1561
                if (!OS_WINDOWS) {
1562
                    $mode = 0666 & ~(int)octdec($this->config->get('umask'));
1563
                    $this->addFileOperation('chmod', array($mode, $copyto));
1564
                    if (!@chmod($copyto, $mode)) {
1565
                        $this->log(0, "failed to change mode of $copyto ($php_errormsg)");
1566
                    }
1567
                }
1568
            }
1569
 
187 mathias 1570
 
1571
            $data = array(
1572
                'role'         => $role,
1573
                'name'         => $bn,
1574
                'installed_as' => $dest,
1575
                'php_api'      => $ext['php_api'],
1576
                'zend_mod_api' => $ext['zend_mod_api'],
1577
                'zend_ext_api' => $ext['zend_ext_api'],
1578
            );
1579
 
94 jpm 1580
            if ($filelist->getPackageXmlVersion() == '1.0') {
187 mathias 1581
                $filelist->installedFile($bn, $data);
94 jpm 1582
            } else {
187 mathias 1583
                $filelist->installedFile($bn, array('attribs' => $data));
94 jpm 1584
            }
1585
        }
1586
    }
1587
 
1588
    // }}}
1589
    function &getUninstallPackages()
1590
    {
1591
        return $this->_downloadedPackages;
1592
    }
1593
    // {{{ uninstall()
1594
 
1595
    /**
1596
     * Uninstall a package
1597
     *
1598
     * This method removes all files installed by the application, and then
1599
     * removes any empty directories.
1600
     * @param string package name
1601
     * @param array Command-line options.  Possibilities include:
1602
     *
1603
     *              - installroot: base installation dir, if not the default
1604
     *              - register-only : update registry but don't remove files
1605
     *              - nodeps: do not process dependencies of other packages to ensure
1606
     *                        uninstallation does not break things
1607
     */
1608
    function uninstall($package, $options = array())
1609
    {
187 mathias 1610
        $installRoot = isset($options['installroot']) ? $options['installroot'] : '';
1611
        $this->config->setInstallRoot($installRoot);
1612
 
1613
        $this->installroot = '';
94 jpm 1614
        $this->_registry = &$this->config->getRegistry();
1615
        if (is_object($package)) {
1616
            $channel = $package->getChannel();
187 mathias 1617
            $pkg     = $package;
94 jpm 1618
            $package = $pkg->getPackage();
1619
        } else {
1620
            $pkg = false;
1621
            $info = $this->_registry->parsePackageName($package,
1622
                $this->config->get('default_channel'));
1623
            $channel = $info['channel'];
1624
            $package = $info['package'];
1625
        }
187 mathias 1626
 
94 jpm 1627
        $savechannel = $this->config->get('default_channel');
1628
        $this->configSet('default_channel', $channel);
1629
        if (!is_object($pkg)) {
1630
            $pkg = $this->_registry->getPackage($package, $channel);
1631
        }
187 mathias 1632
 
94 jpm 1633
        if (!$pkg) {
1634
            $this->configSet('default_channel', $savechannel);
1635
            return $this->raiseError($this->_registry->parsedPackageNameToString(
1636
                array(
1637
                    'channel' => $channel,
1638
                    'package' => $package
1639
                ), true) . ' not installed');
1640
        }
187 mathias 1641
 
94 jpm 1642
        if ($pkg->getInstalledBinary()) {
1643
            // this is just an alias for a binary package
1644
            return $this->_registry->deletePackage($package, $channel);
1645
        }
187 mathias 1646
 
94 jpm 1647
        $filelist = $pkg->getFilelist();
1648
        PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
1649
        if (!class_exists('PEAR_Dependency2')) {
1650
            require_once 'PEAR/Dependency2.php';
1651
        }
187 mathias 1652
 
1653
        $depchecker = new PEAR_Dependency2($this->config, $options,
94 jpm 1654
            array('channel' => $channel, 'package' => $package),
1655
            PEAR_VALIDATE_UNINSTALLING);
1656
        $e = $depchecker->validatePackageUninstall($this);
1657
        PEAR::staticPopErrorHandling();
1658
        if (PEAR::isError($e)) {
1659
            if (!isset($options['ignore-errors'])) {
1660
                return $this->raiseError($e);
1661
            }
187 mathias 1662
 
1663
            if (!isset($options['soft'])) {
1664
                $this->log(0, 'WARNING: ' . $e->getMessage());
1665
            }
94 jpm 1666
        } elseif (is_array($e)) {
1667
            if (!isset($options['soft'])) {
1668
                $this->log(0, $e[0]);
1669
            }
1670
        }
187 mathias 1671
 
94 jpm 1672
        $this->pkginfo = &$pkg;
1673
        // pretty much nothing happens if we are only registering the uninstall
1674
        if (empty($options['register-only'])) {
1675
            // {{{ Delete the files
1676
            $this->startFileTransaction();
1677
            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1678
            if (PEAR::isError($err = $this->_deletePackageFiles($package, $channel))) {
1679
                PEAR::popErrorHandling();
1680
                $this->rollbackFileTransaction();
1681
                $this->configSet('default_channel', $savechannel);
1682
                if (!isset($options['ignore-errors'])) {
1683
                    return $this->raiseError($err);
1684
                }
187 mathias 1685
 
1686
                if (!isset($options['soft'])) {
1687
                    $this->log(0, 'WARNING: ' . $err->getMessage());
1688
                }
94 jpm 1689
            } else {
1690
                PEAR::popErrorHandling();
1691
            }
187 mathias 1692
 
94 jpm 1693
            if (!$this->commitFileTransaction()) {
1694
                $this->rollbackFileTransaction();
1695
                if (!isset($options['ignore-errors'])) {
1696
                    return $this->raiseError("uninstall failed");
187 mathias 1697
                }
1698
 
1699
                if (!isset($options['soft'])) {
94 jpm 1700
                    $this->log(0, 'WARNING: uninstall failed');
1701
                }
1702
            } else {
1703
                $this->startFileTransaction();
187 mathias 1704
                $dirtree = $pkg->getDirTree();
1705
                if ($dirtree === false) {
94 jpm 1706
                    $this->configSet('default_channel', $savechannel);
1707
                    return $this->_registry->deletePackage($package, $channel);
1708
                }
187 mathias 1709
 
1710
                // attempt to delete empty directories
1711
                uksort($dirtree, array($this, '_sortDirs'));
1712
                foreach($dirtree as $dir => $notused) {
1713
                    $this->addFileOperation('rmdir', array($dir));
1714
                }
1715
 
94 jpm 1716
                if (!$this->commitFileTransaction()) {
1717
                    $this->rollbackFileTransaction();
1718
                    if (!isset($options['ignore-errors'])) {
1719
                        return $this->raiseError("uninstall failed");
187 mathias 1720
                    }
1721
 
1722
                    if (!isset($options['soft'])) {
94 jpm 1723
                        $this->log(0, 'WARNING: uninstall failed');
1724
                    }
1725
                }
1726
            }
1727
            // }}}
1728
        }
1729
 
1730
        $this->configSet('default_channel', $savechannel);
1731
        // Register that the package is no longer installed
1732
        return $this->_registry->deletePackage($package, $channel);
1733
    }
1734
 
1735
    /**
1736
     * Sort a list of arrays of array(downloaded packagefilename) by dependency.
1737
     *
1738
     * It also removes duplicate dependencies
1739
     * @param array an array of PEAR_PackageFile_v[1/2] objects
1740
     * @return array|PEAR_Error array of array(packagefilename, package.xml contents)
1741
     */
1742
    function sortPackagesForUninstall(&$packages)
1743
    {
1744
        $this->_dependencyDB = &PEAR_DependencyDB::singleton($this->config);
1745
        if (PEAR::isError($this->_dependencyDB)) {
1746
            return $this->_dependencyDB;
1747
        }
1748
        usort($packages, array(&$this, '_sortUninstall'));
1749
    }
1750
 
1751
    function _sortUninstall($a, $b)
1752
    {
1753
        if (!$a->getDeps() && !$b->getDeps()) {
1754
            return 0; // neither package has dependencies, order is insignificant
1755
        }
1756
        if ($a->getDeps() && !$b->getDeps()) {
1757
            return -1; // $a must be installed after $b because $a has dependencies
1758
        }
1759
        if (!$a->getDeps() && $b->getDeps()) {
1760
            return 1; // $b must be installed after $a because $b has dependencies
1761
        }
1762
        // both packages have dependencies
1763
        if ($this->_dependencyDB->dependsOn($a, $b)) {
1764
            return -1;
1765
        }
1766
        if ($this->_dependencyDB->dependsOn($b, $a)) {
1767
            return 1;
1768
        }
1769
        return 0;
1770
    }
1771
 
1772
    // }}}
1773
    // {{{ _sortDirs()
1774
    function _sortDirs($a, $b)
1775
    {
1776
        if (strnatcmp($a, $b) == -1) return 1;
1777
        if (strnatcmp($a, $b) == 1) return -1;
1778
        return 0;
1779
    }
1780
 
1781
    // }}}
1782
 
1783
    // {{{ _buildCallback()
1784
 
1785
    function _buildCallback($what, $data)
1786
    {
1787
        if (($what == 'cmdoutput' && $this->debug > 1) ||
1788
            ($what == 'output' && $this->debug > 0)) {
1789
            $this->ui->outputData(rtrim($data), 'build');
1790
        }
1791
    }
1792
 
1793
    // }}}
1794
}