Хранилища Subversion ant

Редакция

Содержимое файла | Последнее изменение | Открыть журнал | RSS

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