Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
/**
3
 * PEAR_Downloader_Package
4
 *
5
 * PHP versions 4 and 5
6
 *
7
 * @category   pear
8
 * @package    PEAR
9
 * @author     Greg Beaver <cellog@php.net>
10
 * @copyright  1997-2009 The Authors
11
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12
 * @version    CVS: $Id: Package.php 313023 2011-07-06 19:17:11Z dufuz $
13
 * @link       http://pear.php.net/package/PEAR
14
 * @since      File available since Release 1.4.0a1
15
 */
16
 
17
/**
18
 * Error code when parameter initialization fails because no releases
19
 * exist within preferred_state, but releases do exist
20
 */
21
define('PEAR_DOWNLOADER_PACKAGE_STATE', -1003);
22
/**
23
 * Error code when parameter initialization fails because no releases
24
 * exist that will work with the existing PHP version
25
 */
26
define('PEAR_DOWNLOADER_PACKAGE_PHPVERSION', -1004);
27
 
28
/**
29
 * Coordinates download parameters and manages their dependencies
30
 * prior to downloading them.
31
 *
32
 * Input can come from three sources:
33
 *
34
 * - local files (archives or package.xml)
35
 * - remote files (downloadable urls)
36
 * - abstract package names
37
 *
38
 * The first two elements are handled cleanly by PEAR_PackageFile, but the third requires
39
 * accessing pearweb's xml-rpc interface to determine necessary dependencies, and the
40
 * format returned of dependencies is slightly different from that used in package.xml.
41
 *
42
 * This class hides the differences between these elements, and makes automatic
43
 * dependency resolution a piece of cake.  It also manages conflicts when
44
 * two classes depend on incompatible dependencies, or differing versions of the same
45
 * package dependency.  In addition, download will not be attempted if the php version is
46
 * not supported, PEAR installer version is not supported, or non-PECL extensions are not
47
 * installed.
48
 * @category   pear
49
 * @package    PEAR
50
 * @author     Greg Beaver <cellog@php.net>
51
 * @copyright  1997-2009 The Authors
52
 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
53
 * @version    Release: 1.9.4
54
 * @link       http://pear.php.net/package/PEAR
55
 * @since      Class available since Release 1.4.0a1
56
 */
57
class PEAR_Downloader_Package
58
{
59
    /**
60
     * @var PEAR_Downloader
61
     */
62
    var $_downloader;
63
    /**
64
     * @var PEAR_Config
65
     */
66
    var $_config;
67
    /**
68
     * @var PEAR_Registry
69
     */
70
    var $_registry;
71
    /**
72
     * Used to implement packagingroot properly
73
     * @var PEAR_Registry
74
     */
75
    var $_installRegistry;
76
    /**
77
     * @var PEAR_PackageFile_v1|PEAR_PackageFile|v2
78
     */
79
    var $_packagefile;
80
    /**
81
     * @var array
82
     */
83
    var $_parsedname;
84
    /**
85
     * @var array
86
     */
87
    var $_downloadURL;
88
    /**
89
     * @var array
90
     */
91
    var $_downloadDeps = array();
92
    /**
93
     * @var boolean
94
     */
95
    var $_valid = false;
96
    /**
97
     * @var boolean
98
     */
99
    var $_analyzed = false;
100
    /**
101
     * if this or a parent package was invoked with Package-state, this is set to the
102
     * state variable.
103
     *
104
     * This allows temporary reassignment of preferred_state for a parent package and all of
105
     * its dependencies.
106
     * @var string|false
107
     */
108
    var $_explicitState = false;
109
    /**
110
     * If this package is invoked with Package#group, this variable will be true
111
     */
112
    var $_explicitGroup = false;
113
    /**
114
     * Package type local|url
115
     * @var string
116
     */
117
    var $_type;
118
    /**
119
     * Contents of package.xml, if downloaded from a remote channel
120
     * @var string|false
121
     * @access private
122
     */
123
    var $_rawpackagefile;
124
    /**
125
     * @var boolean
126
     * @access private
127
     */
128
    var $_validated = false;
129
 
130
    /**
131
     * @param PEAR_Downloader
132
     */
133
    function PEAR_Downloader_Package(&$downloader)
134
    {
135
        $this->_downloader = &$downloader;
136
        $this->_config = &$this->_downloader->config;
137
        $this->_registry = &$this->_config->getRegistry();
138
        $options = $downloader->getOptions();
139
        if (isset($options['packagingroot'])) {
140
            $this->_config->setInstallRoot($options['packagingroot']);
141
            $this->_installRegistry = &$this->_config->getRegistry();
142
            $this->_config->setInstallRoot(false);
143
        } else {
144
            $this->_installRegistry = &$this->_registry;
145
        }
146
        $this->_valid = $this->_analyzed = false;
147
    }
148
 
149
    /**
150
     * Parse the input and determine whether this is a local file, a remote uri, or an
151
     * abstract package name.
152
     *
153
     * This is the heart of the PEAR_Downloader_Package(), and is used in
154
     * {@link PEAR_Downloader::download()}
155
     * @param string
156
     * @return bool|PEAR_Error
157
     */
158
    function initialize($param)
159
    {
160
        $origErr = $this->_fromFile($param);
161
        if ($this->_valid) {
162
            return true;
163
        }
164
 
165
        $options = $this->_downloader->getOptions();
166
        if (isset($options['offline'])) {
167
            if (PEAR::isError($origErr) && !isset($options['soft'])) {
168
                foreach ($origErr->getUserInfo() as $userInfo) {
169
                    if (isset($userInfo['message'])) {
170
                        $this->_downloader->log(0, $userInfo['message']);
171
                    }
172
                }
173
 
174
                $this->_downloader->log(0, $origErr->getMessage());
175
            }
176
 
177
            return PEAR::raiseError('Cannot download non-local package "' . $param . '"');
178
        }
179
 
180
        $err = $this->_fromUrl($param);
181
        if (PEAR::isError($err) || !$this->_valid) {
182
            if ($this->_type == 'url') {
183
                if (PEAR::isError($err) && !isset($options['soft'])) {
184
                    $this->_downloader->log(0, $err->getMessage());
185
                }
186
 
187
                return PEAR::raiseError("Invalid or missing remote package file");
188
            }
189
 
190
            $err = $this->_fromString($param);
191
            if (PEAR::isError($err) || !$this->_valid) {
192
                if (PEAR::isError($err) && $err->getCode() == PEAR_DOWNLOADER_PACKAGE_STATE) {
193
                    return false; // instruct the downloader to silently skip
194
                }
195
 
196
                if (isset($this->_type) && $this->_type == 'local' && PEAR::isError($origErr)) {
197
                    if (is_array($origErr->getUserInfo())) {
198
                        foreach ($origErr->getUserInfo() as $err) {
199
                            if (is_array($err)) {
200
                                $err = $err['message'];
201
                            }
202
 
203
                            if (!isset($options['soft'])) {
204
                                $this->_downloader->log(0, $err);
205
                            }
206
                        }
207
                    }
208
 
209
                    if (!isset($options['soft'])) {
210
                        $this->_downloader->log(0, $origErr->getMessage());
211
                    }
212
 
213
                    if (is_array($param)) {
214
                        $param = $this->_registry->parsedPackageNameToString($param, true);
215
                    }
216
 
217
                    if (!isset($options['soft'])) {
218
                        $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
219
                    }
220
 
221
                    // Passing no message back - already logged above
222
                    return PEAR::raiseError();
223
                }
224
 
225
                if (PEAR::isError($err) && !isset($options['soft'])) {
226
                    $this->_downloader->log(0, $err->getMessage());
227
                }
228
 
229
                if (is_array($param)) {
230
                    $param = $this->_registry->parsedPackageNameToString($param, true);
231
                }
232
 
233
                if (!isset($options['soft'])) {
234
                    $this->_downloader->log(2, "Cannot initialize '$param', invalid or missing package file");
235
                }
236
 
237
                // Passing no message back - already logged above
238
                return PEAR::raiseError();
239
            }
240
        }
241
 
242
        return true;
243
    }
244
 
245
    /**
246
     * Retrieve any non-local packages
247
     * @return PEAR_PackageFile_v1|PEAR_PackageFile_v2|PEAR_Error
248
     */
249
    function &download()
250
    {
251
        if (isset($this->_packagefile)) {
252
            return $this->_packagefile;
253
        }
254
 
255
        if (isset($this->_downloadURL['url'])) {
256
            $this->_isvalid = false;
257
            $info = $this->getParsedPackage();
258
            foreach ($info as $i => $p) {
259
                $info[$i] = strtolower($p);
260
            }
261
 
262
            $err = $this->_fromUrl($this->_downloadURL['url'],
263
                $this->_registry->parsedPackageNameToString($this->_parsedname, true));
264
            $newinfo = $this->getParsedPackage();
265
            foreach ($newinfo as $i => $p) {
266
                $newinfo[$i] = strtolower($p);
267
            }
268
 
269
            if ($info != $newinfo) {
270
                do {
271
                    if ($info['channel'] == 'pecl.php.net' && $newinfo['channel'] == 'pear.php.net') {
272
                        $info['channel'] = 'pear.php.net';
273
                        if ($info == $newinfo) {
274
                            // skip the channel check if a pecl package says it's a PEAR package
275
                            break;
276
                        }
277
                    }
278
                    if ($info['channel'] == 'pear.php.net' && $newinfo['channel'] == 'pecl.php.net') {
279
                        $info['channel'] = 'pecl.php.net';
280
                        if ($info == $newinfo) {
281
                            // skip the channel check if a pecl package says it's a PEAR package
282
                            break;
283
                        }
284
                    }
285
 
286
                    return PEAR::raiseError('CRITICAL ERROR: We are ' .
287
                        $this->_registry->parsedPackageNameToString($info) . ', but the file ' .
288
                        'downloaded claims to be ' .
289
                        $this->_registry->parsedPackageNameToString($this->getParsedPackage()));
290
                } while (false);
291
            }
292
 
293
            if (PEAR::isError($err) || !$this->_valid) {
294
                return $err;
295
            }
296
        }
297
 
298
        $this->_type = 'local';
299
        return $this->_packagefile;
300
    }
301
 
302
    function &getPackageFile()
303
    {
304
        return $this->_packagefile;
305
    }
306
 
307
    function &getDownloader()
308
    {
309
        return $this->_downloader;
310
    }
311
 
312
    function getType()
313
    {
314
        return $this->_type;
315
    }
316
 
317
    /**
318
     * Like {@link initialize()}, but operates on a dependency
319
     */
320
    function fromDepURL($dep)
321
    {
322
        $this->_downloadURL = $dep;
323
        if (isset($dep['uri'])) {
324
            $options = $this->_downloader->getOptions();
325
            if (!extension_loaded("zlib") || isset($options['nocompress'])) {
326
                $ext = '.tar';
327
            } else {
328
                $ext = '.tgz';
329
            }
330
 
331
            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
332
            $err = $this->_fromUrl($dep['uri'] . $ext);
333
            PEAR::popErrorHandling();
334
            if (PEAR::isError($err)) {
335
                if (!isset($options['soft'])) {
336
                    $this->_downloader->log(0, $err->getMessage());
337
                }
338
 
339
                return PEAR::raiseError('Invalid uri dependency "' . $dep['uri'] . $ext . '", ' .
340
                    'cannot download');
341
            }
342
        } else {
343
            $this->_parsedname =
344
                array(
345
                    'package' => $dep['info']->getPackage(),
346
                    'channel' => $dep['info']->getChannel(),
347
                    'version' => $dep['version']
348
                );
349
            if (!isset($dep['nodefault'])) {
350
                $this->_parsedname['group'] = 'default'; // download the default dependency group
351
                $this->_explicitGroup = false;
352
            }
353
 
354
            $this->_rawpackagefile = $dep['raw'];
355
        }
356
    }
357
 
358
    function detectDependencies($params)
359
    {
360
        $options = $this->_downloader->getOptions();
361
        if (isset($options['downloadonly'])) {
362
            return;
363
        }
364
 
365
        if (isset($options['offline'])) {
366
            $this->_downloader->log(3, 'Skipping dependency download check, --offline specified');
367
            return;
368
        }
369
 
370
        $pname = $this->getParsedPackage();
371
        if (!$pname) {
372
            return;
373
        }
374
 
375
        $deps = $this->getDeps();
376
        if (!$deps) {
377
            return;
378
        }
379
 
380
        if (isset($deps['required'])) { // package.xml 2.0
381
            return $this->_detect2($deps, $pname, $options, $params);
382
        }
383
 
384
        return $this->_detect1($deps, $pname, $options, $params);
385
    }
386
 
387
    function setValidated()
388
    {
389
        $this->_validated = true;
390
    }
391
 
392
    function alreadyValidated()
393
    {
394
        return $this->_validated;
395
    }
396
 
397
    /**
398
     * Remove packages to be downloaded that are already installed
399
     * @param array of PEAR_Downloader_Package objects
400
     * @static
401
     */
402
    function removeInstalled(&$params)
403
    {
404
        if (!isset($params[0])) {
405
            return;
406
        }
407
 
408
        $options = $params[0]->_downloader->getOptions();
409
        if (!isset($options['downloadonly'])) {
410
            foreach ($params as $i => $param) {
411
                $package = $param->getPackage();
412
                $channel = $param->getChannel();
413
                // remove self if already installed with this version
414
                // this does not need any pecl magic - we only remove exact matches
415
                if ($param->_installRegistry->packageExists($package, $channel)) {
416
                    $packageVersion = $param->_installRegistry->packageInfo($package, 'version', $channel);
417
                    if (version_compare($packageVersion, $param->getVersion(), '==')) {
418
                        if (!isset($options['force'])) {
419
                            $info = $param->getParsedPackage();
420
                            unset($info['version']);
421
                            unset($info['state']);
422
                            if (!isset($options['soft'])) {
423
                                $param->_downloader->log(1, 'Skipping package "' .
424
                                    $param->getShortName() .
425
                                    '", already installed as version ' . $packageVersion);
426
                            }
427
                            $params[$i] = false;
428
                        }
429
                    } elseif (!isset($options['force']) && !isset($options['upgrade']) &&
430
                          !isset($options['soft'])) {
431
                        $info = $param->getParsedPackage();
432
                        $param->_downloader->log(1, 'Skipping package "' .
433
                            $param->getShortName() .
434
                            '", already installed as version ' . $packageVersion);
435
                        $params[$i] = false;
436
                    }
437
                }
438
            }
439
        }
440
 
441
        PEAR_Downloader_Package::removeDuplicates($params);
442
    }
443
 
444
    function _detect2($deps, $pname, $options, $params)
445
    {
446
        $this->_downloadDeps = array();
447
        $groupnotfound = false;
448
        foreach (array('package', 'subpackage') as $packagetype) {
449
            // get required dependency group
450
            if (isset($deps['required'][$packagetype])) {
451
                if (isset($deps['required'][$packagetype][0])) {
452
                    foreach ($deps['required'][$packagetype] as $dep) {
453
                        if (isset($dep['conflicts'])) {
454
                            // skip any package that this package conflicts with
455
                            continue;
456
                        }
457
                        $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
458
                        if (is_array($ret)) {
459
                            $this->_downloadDeps[] = $ret;
460
                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
461
                            $this->_downloader->log(0, $ret->getMessage());
462
                        }
463
                    }
464
                } else {
465
                    $dep = $deps['required'][$packagetype];
466
                    if (!isset($dep['conflicts'])) {
467
                        // skip any package that this package conflicts with
468
                        $ret = $this->_detect2Dep($dep, $pname, 'required', $params);
469
                        if (is_array($ret)) {
470
                            $this->_downloadDeps[] = $ret;
471
                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
472
                            $this->_downloader->log(0, $ret->getMessage());
473
                        }
474
                    }
475
                }
476
            }
477
 
478
            // get optional dependency group, if any
479
            if (isset($deps['optional'][$packagetype])) {
480
                $skipnames = array();
481
                if (!isset($deps['optional'][$packagetype][0])) {
482
                    $deps['optional'][$packagetype] = array($deps['optional'][$packagetype]);
483
                }
484
 
485
                foreach ($deps['optional'][$packagetype] as $dep) {
486
                    $skip = false;
487
                    if (!isset($options['alldeps'])) {
488
                        $dep['package'] = $dep['name'];
489
                        if (!isset($options['soft'])) {
490
                            $this->_downloader->log(3, 'Notice: package "' .
491
                              $this->_registry->parsedPackageNameToString($this->getParsedPackage(),
492
                                    true) . '" optional dependency "' .
493
                                $this->_registry->parsedPackageNameToString(array('package' =>
494
                                    $dep['name'], 'channel' => 'pear.php.net'), true) .
495
                                '" will not be automatically downloaded');
496
                        }
497
                        $skipnames[] = $this->_registry->parsedPackageNameToString($dep, true);
498
                        $skip = true;
499
                        unset($dep['package']);
500
                    }
501
 
502
                    $ret = $this->_detect2Dep($dep, $pname, 'optional', $params);
503
                    if (PEAR::isError($ret) && !isset($options['soft'])) {
504
                        $this->_downloader->log(0, $ret->getMessage());
505
                    }
506
 
507
                    if (!$ret) {
508
                        $dep['package'] = $dep['name'];
509
                        $skip = count($skipnames) ?
510
                            $skipnames[count($skipnames) - 1] : '';
511
                        if ($skip ==
512
                              $this->_registry->parsedPackageNameToString($dep, true)) {
513
                            array_pop($skipnames);
514
                        }
515
                    }
516
 
517
                    if (!$skip && is_array($ret)) {
518
                        $this->_downloadDeps[] = $ret;
519
                    }
520
                }
521
 
522
                if (count($skipnames)) {
523
                    if (!isset($options['soft'])) {
524
                        $this->_downloader->log(1, 'Did not download optional dependencies: ' .
525
                            implode(', ', $skipnames) .
526
                            ', use --alldeps to download automatically');
527
                    }
528
                }
529
            }
530
 
531
            // get requested dependency group, if any
532
            $groupname = $this->getGroup();
533
            $explicit  = $this->_explicitGroup;
534
            if (!$groupname) {
535
                if (!$this->canDefault()) {
536
                    continue;
537
                }
538
 
539
                $groupname = 'default'; // try the default dependency group
540
            }
541
 
542
            if ($groupnotfound) {
543
                continue;
544
            }
545
 
546
            if (isset($deps['group'])) {
547
                if (isset($deps['group']['attribs'])) {
548
                    if (strtolower($deps['group']['attribs']['name']) == strtolower($groupname)) {
549
                        $group = $deps['group'];
550
                    } elseif ($explicit) {
551
                        if (!isset($options['soft'])) {
552
                            $this->_downloader->log(0, 'Warning: package "' .
553
                                $this->_registry->parsedPackageNameToString($pname, true) .
554
                                '" has no dependency ' . 'group named "' . $groupname . '"');
555
                        }
556
 
557
                        $groupnotfound = true;
558
                        continue;
559
                    }
560
                } else {
561
                    $found = false;
562
                    foreach ($deps['group'] as $group) {
563
                        if (strtolower($group['attribs']['name']) == strtolower($groupname)) {
564
                            $found = true;
565
                            break;
566
                        }
567
                    }
568
 
569
                    if (!$found) {
570
                        if ($explicit) {
571
                            if (!isset($options['soft'])) {
572
                                $this->_downloader->log(0, 'Warning: package "' .
573
                                    $this->_registry->parsedPackageNameToString($pname, true) .
574
                                    '" has no dependency ' . 'group named "' . $groupname . '"');
575
                            }
576
                        }
577
 
578
                        $groupnotfound = true;
579
                        continue;
580
                    }
581
                }
582
            }
583
 
584
            if (isset($group) && isset($group[$packagetype])) {
585
                if (isset($group[$packagetype][0])) {
586
                    foreach ($group[$packagetype] as $dep) {
587
                        $ret = $this->_detect2Dep($dep, $pname, 'dependency group "' .
588
                            $group['attribs']['name'] . '"', $params);
589
                        if (is_array($ret)) {
590
                            $this->_downloadDeps[] = $ret;
591
                        } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
592
                            $this->_downloader->log(0, $ret->getMessage());
593
                        }
594
                    }
595
                } else {
596
                    $ret = $this->_detect2Dep($group[$packagetype], $pname,
597
                        'dependency group "' .
598
                        $group['attribs']['name'] . '"', $params);
599
                    if (is_array($ret)) {
600
                        $this->_downloadDeps[] = $ret;
601
                    } elseif (PEAR::isError($ret) && !isset($options['soft'])) {
602
                        $this->_downloader->log(0, $ret->getMessage());
603
                    }
604
                }
605
            }
606
        }
607
    }
608
 
609
    function _detect2Dep($dep, $pname, $group, $params)
610
    {
611
        if (isset($dep['conflicts'])) {
612
            return true;
613
        }
614
 
615
        $options = $this->_downloader->getOptions();
616
        if (isset($dep['uri'])) {
617
            return array('uri' => $dep['uri'], 'dep' => $dep);;
618
        }
619
 
620
        $testdep = $dep;
621
        $testdep['package'] = $dep['name'];
622
        if (PEAR_Downloader_Package::willDownload($testdep, $params)) {
623
            $dep['package'] = $dep['name'];
624
            if (!isset($options['soft'])) {
625
                $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group .
626
                    ' dependency "' .
627
                    $this->_registry->parsedPackageNameToString($dep, true) .
628
                    '", will be installed');
629
            }
630
            return false;
631
        }
632
 
633
        $options = $this->_downloader->getOptions();
634
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
635
        if ($this->_explicitState) {
636
            $pname['state'] = $this->_explicitState;
637
        }
638
 
639
        $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
640
        if (PEAR::isError($url)) {
641
            PEAR::popErrorHandling();
642
            return $url;
643
        }
644
 
645
        $dep['package'] = $dep['name'];
646
        $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params, $group == 'optional' &&
647
            !isset($options['alldeps']), true);
648
        PEAR::popErrorHandling();
649
        if (PEAR::isError($ret)) {
650
            if (!isset($options['soft'])) {
651
                $this->_downloader->log(0, $ret->getMessage());
652
            }
653
 
654
            return false;
655
        }
656
 
657
        // check to see if a dep is already installed and is the same or newer
658
        if (!isset($dep['min']) && !isset($dep['max']) && !isset($dep['recommended'])) {
659
            $oper = 'has';
660
        } else {
661
            $oper = 'gt';
662
        }
663
 
664
        // do not try to move this before getDepPackageDownloadURL
665
        // we can't determine whether upgrade is necessary until we know what
666
        // version would be downloaded
667
        if (!isset($options['force']) && $this->isInstalled($ret, $oper)) {
668
            $version = $this->_installRegistry->packageInfo($dep['name'], 'version', $dep['channel']);
669
            $dep['package'] = $dep['name'];
670
            if (!isset($options['soft'])) {
671
                $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
672
                    ' dependency "' .
673
                $this->_registry->parsedPackageNameToString($dep, true) .
674
                    '" version ' . $url['version'] . ', already installed as version ' .
675
                    $version);
676
            }
677
 
678
            return false;
679
        }
680
 
681
        if (isset($dep['nodefault'])) {
682
            $ret['nodefault'] = true;
683
        }
684
 
685
        return $ret;
686
    }
687
 
688
    function _detect1($deps, $pname, $options, $params)
689
    {
690
        $this->_downloadDeps = array();
691
        $skipnames = array();
692
        foreach ($deps as $dep) {
693
            $nodownload = false;
694
            if (isset ($dep['type']) && $dep['type'] === 'pkg') {
695
                $dep['channel'] = 'pear.php.net';
696
                $dep['package'] = $dep['name'];
697
                switch ($dep['rel']) {
698
                    case 'not' :
699
                        continue 2;
700
                    case 'ge' :
701
                    case 'eq' :
702
                    case 'gt' :
703
                    case 'has' :
704
                        $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
705
                            'required' :
706
                            'optional';
707
                        if (PEAR_Downloader_Package::willDownload($dep, $params)) {
708
                            $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
709
                                . ' dependency "' .
710
                                $this->_registry->parsedPackageNameToString($dep, true) .
711
                                '", will be installed');
712
                            continue 2;
713
                        }
714
                        $fakedp = new PEAR_PackageFile_v1;
715
                        $fakedp->setPackage($dep['name']);
716
                        // skip internet check if we are not upgrading (bug #5810)
717
                        if (!isset($options['upgrade']) && $this->isInstalled(
718
                              $fakedp, $dep['rel'])) {
719
                            $this->_downloader->log(2, $this->getShortName() . ': Skipping ' . $group
720
                                . ' dependency "' .
721
                                $this->_registry->parsedPackageNameToString($dep, true) .
722
                                '", is already installed');
723
                            continue 2;
724
                        }
725
                }
726
 
727
                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
728
                if ($this->_explicitState) {
729
                    $pname['state'] = $this->_explicitState;
730
                }
731
 
732
                $url = $this->_downloader->_getDepPackageDownloadUrl($dep, $pname);
733
                $chan = 'pear.php.net';
734
                if (PEAR::isError($url)) {
735
                    // check to see if this is a pecl package that has jumped
736
                    // from pear.php.net to pecl.php.net channel
737
                    if (!class_exists('PEAR_Dependency2')) {
738
                        require_once 'PEAR/Dependency2.php';
739
                    }
740
 
741
                    $newdep = PEAR_Dependency2::normalizeDep($dep);
742
                    $newdep = $newdep[0];
743
                    $newdep['channel'] = 'pecl.php.net';
744
                    $chan = 'pecl.php.net';
745
                    $url = $this->_downloader->_getDepPackageDownloadUrl($newdep, $pname);
746
                    $obj = &$this->_installRegistry->getPackage($dep['name']);
747
                    if (PEAR::isError($url)) {
748
                        PEAR::popErrorHandling();
749
                        if ($obj !== null && $this->isInstalled($obj, $dep['rel'])) {
750
                            $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
751
                                'required' :
752
                                'optional';
753
                            $dep['package'] = $dep['name'];
754
                            if (!isset($options['soft'])) {
755
                                $this->_downloader->log(3, $this->getShortName() .
756
                                    ': Skipping ' . $group . ' dependency "' .
757
                                    $this->_registry->parsedPackageNameToString($dep, true) .
758
                                    '", already installed as version ' . $obj->getVersion());
759
                            }
760
                            $skip = count($skipnames) ?
761
                                $skipnames[count($skipnames) - 1] : '';
762
                            if ($skip ==
763
                                  $this->_registry->parsedPackageNameToString($dep, true)) {
764
                                array_pop($skipnames);
765
                            }
766
                            continue;
767
                        } else {
768
                            if (isset($dep['optional']) && $dep['optional'] == 'yes') {
769
                                $this->_downloader->log(2, $this->getShortName() .
770
                                    ': Skipping optional dependency "' .
771
                                    $this->_registry->parsedPackageNameToString($dep, true) .
772
                                    '", no releases exist');
773
                                continue;
774
                            } else {
775
                                return $url;
776
                            }
777
                        }
778
                    }
779
                }
780
 
781
                PEAR::popErrorHandling();
782
                if (!isset($options['alldeps'])) {
783
                    if (isset($dep['optional']) && $dep['optional'] == 'yes') {
784
                        if (!isset($options['soft'])) {
785
                            $this->_downloader->log(3, 'Notice: package "' .
786
                                $this->getShortName() .
787
                                '" optional dependency "' .
788
                                $this->_registry->parsedPackageNameToString(
789
                                    array('channel' => $chan, 'package' =>
790
                                    $dep['name']), true) .
791
                                '" will not be automatically downloaded');
792
                        }
793
                        $skipnames[] = $this->_registry->parsedPackageNameToString(
794
                                array('channel' => $chan, 'package' =>
795
                                $dep['name']), true);
796
                        $nodownload = true;
797
                    }
798
                }
799
 
800
                if (!isset($options['alldeps']) && !isset($options['onlyreqdeps'])) {
801
                    if (!isset($dep['optional']) || $dep['optional'] == 'no') {
802
                        if (!isset($options['soft'])) {
803
                            $this->_downloader->log(3, 'Notice: package "' .
804
                                $this->getShortName() .
805
                                '" required dependency "' .
806
                                $this->_registry->parsedPackageNameToString(
807
                                    array('channel' => $chan, 'package' =>
808
                                    $dep['name']), true) .
809
                                '" will not be automatically downloaded');
810
                        }
811
                        $skipnames[] = $this->_registry->parsedPackageNameToString(
812
                                array('channel' => $chan, 'package' =>
813
                                $dep['name']), true);
814
                        $nodownload = true;
815
                    }
816
                }
817
 
818
                // check to see if a dep is already installed
819
                // do not try to move this before getDepPackageDownloadURL
820
                // we can't determine whether upgrade is necessary until we know what
821
                // version would be downloaded
822
                if (!isset($options['force']) && $this->isInstalled(
823
                        $url, $dep['rel'])) {
824
                    $group = (!isset($dep['optional']) || $dep['optional'] == 'no') ?
825
                        'required' :
826
                        'optional';
827
                    $dep['package'] = $dep['name'];
828
                    if (isset($newdep)) {
829
                        $version = $this->_installRegistry->packageInfo($newdep['name'], 'version', $newdep['channel']);
830
                    } else {
831
                        $version = $this->_installRegistry->packageInfo($dep['name'], 'version');
832
                    }
833
 
834
                    $dep['version'] = $url['version'];
835
                    if (!isset($options['soft'])) {
836
                        $this->_downloader->log(3, $this->getShortName() . ': Skipping ' . $group .
837
                            ' dependency "' .
838
                            $this->_registry->parsedPackageNameToString($dep, true) .
839
                            '", already installed as version ' . $version);
840
                    }
841
 
842
                    $skip = count($skipnames) ?
843
                        $skipnames[count($skipnames) - 1] : '';
844
                    if ($skip ==
845
                          $this->_registry->parsedPackageNameToString($dep, true)) {
846
                        array_pop($skipnames);
847
                    }
848
 
849
                    continue;
850
                }
851
 
852
                if ($nodownload) {
853
                    continue;
854
                }
855
 
856
                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
857
                if (isset($newdep)) {
858
                    $dep = $newdep;
859
                }
860
 
861
                $dep['package'] = $dep['name'];
862
                $ret = $this->_analyzeDownloadURL($url, 'dependency', $dep, $params,
863
                    isset($dep['optional']) && $dep['optional'] == 'yes' &&
864
                    !isset($options['alldeps']), true);
865
                PEAR::popErrorHandling();
866
                if (PEAR::isError($ret)) {
867
                    if (!isset($options['soft'])) {
868
                        $this->_downloader->log(0, $ret->getMessage());
869
                    }
870
                    continue;
871
                }
872
 
873
                $this->_downloadDeps[] = $ret;
874
            }
875
        }
876
 
877
        if (count($skipnames)) {
878
            if (!isset($options['soft'])) {
879
                $this->_downloader->log(1, 'Did not download dependencies: ' .
880
                    implode(', ', $skipnames) .
881
                    ', use --alldeps or --onlyreqdeps to download automatically');
882
            }
883
        }
884
    }
885
 
886
    function setDownloadURL($pkg)
887
    {
888
        $this->_downloadURL = $pkg;
889
    }
890
 
891
    /**
892
     * Set the package.xml object for this downloaded package
893
     *
894
     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 $pkg
895
     */
896
    function setPackageFile(&$pkg)
897
    {
898
        $this->_packagefile = &$pkg;
899
    }
900
 
901
    function getShortName()
902
    {
903
        return $this->_registry->parsedPackageNameToString(array('channel' => $this->getChannel(),
904
            'package' => $this->getPackage()), true);
905
    }
906
 
907
    function getParsedPackage()
908
    {
909
        if (isset($this->_packagefile) || isset($this->_parsedname)) {
910
            return array('channel' => $this->getChannel(),
911
                'package' => $this->getPackage(),
912
                'version' => $this->getVersion());
913
        }
914
 
915
        return false;
916
    }
917
 
918
    function getDownloadURL()
919
    {
920
        return $this->_downloadURL;
921
    }
922
 
923
    function canDefault()
924
    {
925
        if (isset($this->_downloadURL) && isset($this->_downloadURL['nodefault'])) {
926
            return false;
927
        }
928
 
929
        return true;
930
    }
931
 
932
    function getPackage()
933
    {
934
        if (isset($this->_packagefile)) {
935
            return $this->_packagefile->getPackage();
936
        } elseif (isset($this->_downloadURL['info'])) {
937
            return $this->_downloadURL['info']->getPackage();
938
        }
939
 
940
        return false;
941
    }
942
 
943
    /**
944
     * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2
945
     */
946
    function isSubpackage(&$pf)
947
    {
948
        if (isset($this->_packagefile)) {
949
            return $this->_packagefile->isSubpackage($pf);
950
        } elseif (isset($this->_downloadURL['info'])) {
951
            return $this->_downloadURL['info']->isSubpackage($pf);
952
        }
953
 
954
        return false;
955
    }
956
 
957
    function getPackageType()
958
    {
959
        if (isset($this->_packagefile)) {
960
            return $this->_packagefile->getPackageType();
961
        } elseif (isset($this->_downloadURL['info'])) {
962
            return $this->_downloadURL['info']->getPackageType();
963
        }
964
 
965
        return false;
966
    }
967
 
968
    function isBundle()
969
    {
970
        if (isset($this->_packagefile)) {
971
            return $this->_packagefile->getPackageType() == 'bundle';
972
        }
973
 
974
        return false;
975
    }
976
 
977
    function getPackageXmlVersion()
978
    {
979
        if (isset($this->_packagefile)) {
980
            return $this->_packagefile->getPackagexmlVersion();
981
        } elseif (isset($this->_downloadURL['info'])) {
982
            return $this->_downloadURL['info']->getPackagexmlVersion();
983
        }
984
 
985
        return '1.0';
986
    }
987
 
988
    function getChannel()
989
    {
990
        if (isset($this->_packagefile)) {
991
            return $this->_packagefile->getChannel();
992
        } elseif (isset($this->_downloadURL['info'])) {
993
            return $this->_downloadURL['info']->getChannel();
994
        }
995
 
996
        return false;
997
    }
998
 
999
    function getURI()
1000
    {
1001
        if (isset($this->_packagefile)) {
1002
            return $this->_packagefile->getURI();
1003
        } elseif (isset($this->_downloadURL['info'])) {
1004
            return $this->_downloadURL['info']->getURI();
1005
        }
1006
 
1007
        return false;
1008
    }
1009
 
1010
    function getVersion()
1011
    {
1012
        if (isset($this->_packagefile)) {
1013
            return $this->_packagefile->getVersion();
1014
        } elseif (isset($this->_downloadURL['version'])) {
1015
            return $this->_downloadURL['version'];
1016
        }
1017
 
1018
        return false;
1019
    }
1020
 
1021
    function isCompatible($pf)
1022
    {
1023
        if (isset($this->_packagefile)) {
1024
            return $this->_packagefile->isCompatible($pf);
1025
        } elseif (isset($this->_downloadURL['info'])) {
1026
            return $this->_downloadURL['info']->isCompatible($pf);
1027
        }
1028
 
1029
        return true;
1030
    }
1031
 
1032
    function setGroup($group)
1033
    {
1034
        $this->_parsedname['group'] = $group;
1035
    }
1036
 
1037
    function getGroup()
1038
    {
1039
        if (isset($this->_parsedname['group'])) {
1040
            return $this->_parsedname['group'];
1041
        }
1042
 
1043
        return '';
1044
    }
1045
 
1046
    function isExtension($name)
1047
    {
1048
        if (isset($this->_packagefile)) {
1049
            return $this->_packagefile->isExtension($name);
1050
        } elseif (isset($this->_downloadURL['info'])) {
1051
            if ($this->_downloadURL['info']->getPackagexmlVersion() == '2.0') {
1052
                return $this->_downloadURL['info']->getProvidesExtension() == $name;
1053
            }
1054
 
1055
            return false;
1056
        }
1057
 
1058
        return false;
1059
    }
1060
 
1061
    function getDeps()
1062
    {
1063
        if (isset($this->_packagefile)) {
1064
            $ver = $this->_packagefile->getPackagexmlVersion();
1065
            if (version_compare($ver, '2.0', '>=')) {
1066
                return $this->_packagefile->getDeps(true);
1067
            }
1068
 
1069
            return $this->_packagefile->getDeps();
1070
        } elseif (isset($this->_downloadURL['info'])) {
1071
            $ver = $this->_downloadURL['info']->getPackagexmlVersion();
1072
            if (version_compare($ver, '2.0', '>=')) {
1073
                return $this->_downloadURL['info']->getDeps(true);
1074
            }
1075
 
1076
            return $this->_downloadURL['info']->getDeps();
1077
        }
1078
 
1079
        return array();
1080
    }
1081
 
1082
    /**
1083
     * @param array Parsed array from {@link PEAR_Registry::parsePackageName()} or a dependency
1084
     *                     returned from getDepDownloadURL()
1085
     */
1086
    function isEqual($param)
1087
    {
1088
        if (is_object($param)) {
1089
            $channel = $param->getChannel();
1090
            $package = $param->getPackage();
1091
            if ($param->getURI()) {
1092
                $param = array(
1093
                    'channel' => $param->getChannel(),
1094
                    'package' => $param->getPackage(),
1095
                    'version' => $param->getVersion(),
1096
                    'uri' => $param->getURI(),
1097
                );
1098
            } else {
1099
                $param = array(
1100
                    'channel' => $param->getChannel(),
1101
                    'package' => $param->getPackage(),
1102
                    'version' => $param->getVersion(),
1103
                );
1104
            }
1105
        } else {
1106
            if (isset($param['uri'])) {
1107
                if ($this->getChannel() != '__uri') {
1108
                    return false;
1109
                }
1110
                return $param['uri'] == $this->getURI();
1111
            }
1112
 
1113
            $package = isset($param['package']) ? $param['package'] : $param['info']->getPackage();
1114
            $channel = isset($param['channel']) ? $param['channel'] : $param['info']->getChannel();
1115
            if (isset($param['rel'])) {
1116
                if (!class_exists('PEAR_Dependency2')) {
1117
                    require_once 'PEAR/Dependency2.php';
1118
                }
1119
 
1120
                $newdep = PEAR_Dependency2::normalizeDep($param);
1121
                $newdep = $newdep[0];
1122
            } elseif (isset($param['min'])) {
1123
                $newdep = $param;
1124
            }
1125
        }
1126
 
1127
        if (isset($newdep)) {
1128
            if (!isset($newdep['min'])) {
1129
                $newdep['min'] = '0';
1130
            }
1131
 
1132
            if (!isset($newdep['max'])) {
1133
                $newdep['max'] = '100000000000000000000';
1134
            }
1135
 
1136
            // use magic to support pecl packages suddenly jumping to the pecl channel
1137
            // we need to support both dependency possibilities
1138
            if ($channel == 'pear.php.net' && $this->getChannel() == 'pecl.php.net') {
1139
                if ($package == $this->getPackage()) {
1140
                    $channel = 'pecl.php.net';
1141
                }
1142
            }
1143
            if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
1144
                if ($package == $this->getPackage()) {
1145
                    $channel = 'pear.php.net';
1146
                }
1147
            }
1148
 
1149
            return (strtolower($package) == strtolower($this->getPackage()) &&
1150
                $channel == $this->getChannel() &&
1151
                version_compare($newdep['min'], $this->getVersion(), '<=') &&
1152
                version_compare($newdep['max'], $this->getVersion(), '>='));
1153
        }
1154
 
1155
        // use magic to support pecl packages suddenly jumping to the pecl channel
1156
        if ($channel == 'pecl.php.net' && $this->getChannel() == 'pear.php.net') {
1157
            if (strtolower($package) == strtolower($this->getPackage())) {
1158
                $channel = 'pear.php.net';
1159
            }
1160
        }
1161
 
1162
        if (isset($param['version'])) {
1163
            return (strtolower($package) == strtolower($this->getPackage()) &&
1164
                $channel == $this->getChannel() &&
1165
                $param['version'] == $this->getVersion());
1166
        }
1167
 
1168
        return strtolower($package) == strtolower($this->getPackage()) &&
1169
            $channel == $this->getChannel();
1170
    }
1171
 
1172
    function isInstalled($dep, $oper = '==')
1173
    {
1174
        if (!$dep) {
1175
            return false;
1176
        }
1177
 
1178
        if ($oper != 'ge' && $oper != 'gt' && $oper != 'has' && $oper != '==') {
1179
            return false;
1180
        }
1181
 
1182
        if (is_object($dep)) {
1183
            $package = $dep->getPackage();
1184
            $channel = $dep->getChannel();
1185
            if ($dep->getURI()) {
1186
                $dep = array(
1187
                    'uri' => $dep->getURI(),
1188
                    'version' => $dep->getVersion(),
1189
                );
1190
            } else {
1191
                $dep = array(
1192
                    'version' => $dep->getVersion(),
1193
                );
1194
            }
1195
        } else {
1196
            if (isset($dep['uri'])) {
1197
                $channel = '__uri';
1198
                $package = $dep['dep']['name'];
1199
            } else {
1200
                $channel = $dep['info']->getChannel();
1201
                $package = $dep['info']->getPackage();
1202
            }
1203
        }
1204
 
1205
        $options = $this->_downloader->getOptions();
1206
        $test    = $this->_installRegistry->packageExists($package, $channel);
1207
        if (!$test && $channel == 'pecl.php.net') {
1208
            // do magic to allow upgrading from old pecl packages to new ones
1209
            $test = $this->_installRegistry->packageExists($package, 'pear.php.net');
1210
            $channel = 'pear.php.net';
1211
        }
1212
 
1213
        if ($test) {
1214
            if (isset($dep['uri'])) {
1215
                if ($this->_installRegistry->packageInfo($package, 'uri', '__uri') == $dep['uri']) {
1216
                    return true;
1217
                }
1218
            }
1219
 
1220
            if (isset($options['upgrade'])) {
1221
                $packageVersion = $this->_installRegistry->packageInfo($package, 'version', $channel);
1222
                if (version_compare($packageVersion, $dep['version'], '>=')) {
1223
                    return true;
1224
                }
1225
 
1226
                return false;
1227
            }
1228
 
1229
            return true;
1230
        }
1231
 
1232
        return false;
1233
    }
1234
 
1235
    /**
1236
     * Detect duplicate package names with differing versions
1237
     *
1238
     * If a user requests to install Date 1.4.6 and Date 1.4.7,
1239
     * for instance, this is a logic error.  This method
1240
     * detects this situation.
1241
     *
1242
     * @param array $params array of PEAR_Downloader_Package objects
1243
     * @param array $errorparams empty array
1244
     * @return array array of stupid duplicated packages in PEAR_Downloader_Package obejcts
1245
     */
1246
    function detectStupidDuplicates($params, &$errorparams)
1247
    {
1248
        $existing = array();
1249
        foreach ($params as $i => $param) {
1250
            $package = $param->getPackage();
1251
            $channel = $param->getChannel();
1252
            $group   = $param->getGroup();
1253
            if (!isset($existing[$channel . '/' . $package])) {
1254
                $existing[$channel . '/' . $package] = array();
1255
            }
1256
 
1257
            if (!isset($existing[$channel . '/' . $package][$group])) {
1258
                $existing[$channel . '/' . $package][$group] = array();
1259
            }
1260
 
1261
            $existing[$channel . '/' . $package][$group][] = $i;
1262
        }
1263
 
1264
        $indices = array();
1265
        foreach ($existing as $package => $groups) {
1266
            foreach ($groups as $group => $dupes) {
1267
                if (count($dupes) > 1) {
1268
                    $indices = $indices + $dupes;
1269
                }
1270
            }
1271
        }
1272
 
1273
        $indices = array_unique($indices);
1274
        foreach ($indices as $index) {
1275
            $errorparams[] = $params[$index];
1276
        }
1277
 
1278
        return count($errorparams);
1279
    }
1280
 
1281
    /**
1282
     * @param array
1283
     * @param bool ignore install groups - for final removal of dupe packages
1284
     * @static
1285
     */
1286
    function removeDuplicates(&$params, $ignoreGroups = false)
1287
    {
1288
        $pnames = array();
1289
        foreach ($params as $i => $param) {
1290
            if (!$param) {
1291
                continue;
1292
            }
1293
 
1294
            if ($param->getPackage()) {
1295
                $group = $ignoreGroups ? '' : $param->getGroup();
1296
                $pnames[$i] = $param->getChannel() . '/' .
1297
                    $param->getPackage() . '-' . $param->getVersion() . '#' . $group;
1298
            }
1299
        }
1300
 
1301
        $pnames = array_unique($pnames);
1302
        $unset  = array_diff(array_keys($params), array_keys($pnames));
1303
        $testp  = array_flip($pnames);
1304
        foreach ($params as $i => $param) {
1305
            if (!$param) {
1306
                $unset[] = $i;
1307
                continue;
1308
            }
1309
 
1310
            if (!is_a($param, 'PEAR_Downloader_Package')) {
1311
                $unset[] = $i;
1312
                continue;
1313
            }
1314
 
1315
            $group = $ignoreGroups ? '' : $param->getGroup();
1316
            if (!isset($testp[$param->getChannel() . '/' . $param->getPackage() . '-' .
1317
                  $param->getVersion() . '#' . $group])) {
1318
                $unset[] = $i;
1319
            }
1320
        }
1321
 
1322
        foreach ($unset as $i) {
1323
            unset($params[$i]);
1324
        }
1325
 
1326
        $ret = array();
1327
        foreach ($params as $i => $param) {
1328
            $ret[] = &$params[$i];
1329
        }
1330
 
1331
        $params = array();
1332
        foreach ($ret as $i => $param) {
1333
            $params[] = &$ret[$i];
1334
        }
1335
    }
1336
 
1337
    function explicitState()
1338
    {
1339
        return $this->_explicitState;
1340
    }
1341
 
1342
    function setExplicitState($s)
1343
    {
1344
        $this->_explicitState = $s;
1345
    }
1346
 
1347
    /**
1348
     * @static
1349
     */
1350
    function mergeDependencies(&$params)
1351
    {
1352
        $bundles = $newparams = array();
1353
        foreach ($params as $i => $param) {
1354
            if (!$param->isBundle()) {
1355
                continue;
1356
            }
1357
 
1358
            $bundles[] = $i;
1359
            $pf = &$param->getPackageFile();
1360
            $newdeps = array();
1361
            $contents = $pf->getBundledPackages();
1362
            if (!is_array($contents)) {
1363
                $contents = array($contents);
1364
            }
1365
 
1366
            foreach ($contents as $file) {
1367
                $filecontents = $pf->getFileContents($file);
1368
                $dl = &$param->getDownloader();
1369
                $options = $dl->getOptions();
1370
                if (PEAR::isError($dir = $dl->getDownloadDir())) {
1371
                    return $dir;
1372
                }
1373
 
1374
                $fp = @fopen($dir . DIRECTORY_SEPARATOR . $file, 'wb');
1375
                if (!$fp) {
1376
                    continue;
1377
                }
1378
 
1379
                // FIXME do symlink check
1380
 
1381
                fwrite($fp, $filecontents, strlen($filecontents));
1382
                fclose($fp);
1383
                if ($s = $params[$i]->explicitState()) {
1384
                    $obj->setExplicitState($s);
1385
                }
1386
 
1387
                $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
1388
                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1389
                if (PEAR::isError($dir = $dl->getDownloadDir())) {
1390
                    PEAR::popErrorHandling();
1391
                    return $dir;
1392
                }
1393
 
1394
                $e = $obj->_fromFile($a = $dir . DIRECTORY_SEPARATOR . $file);
1395
                PEAR::popErrorHandling();
1396
                if (PEAR::isError($e)) {
1397
                    if (!isset($options['soft'])) {
1398
                        $dl->log(0, $e->getMessage());
1399
                    }
1400
                    continue;
1401
                }
1402
 
1403
                $j = &$obj;
1404
                if (!PEAR_Downloader_Package::willDownload($j,
1405
                      array_merge($params, $newparams)) && !$param->isInstalled($j)) {
1406
                    $newparams[] = &$j;
1407
                }
1408
            }
1409
        }
1410
 
1411
        foreach ($bundles as $i) {
1412
            unset($params[$i]); // remove bundles - only their contents matter for installation
1413
        }
1414
 
1415
        PEAR_Downloader_Package::removeDuplicates($params); // strip any unset indices
1416
        if (count($newparams)) { // add in bundled packages for install
1417
            foreach ($newparams as $i => $unused) {
1418
                $params[] = &$newparams[$i];
1419
            }
1420
            $newparams = array();
1421
        }
1422
 
1423
        foreach ($params as $i => $param) {
1424
            $newdeps = array();
1425
            foreach ($param->_downloadDeps as $dep) {
1426
                $merge = array_merge($params, $newparams);
1427
                if (!PEAR_Downloader_Package::willDownload($dep, $merge)
1428
                    && !$param->isInstalled($dep)
1429
                ) {
1430
                    $newdeps[] = $dep;
1431
                } else {
1432
                    //var_dump($dep);
1433
                    // detect versioning conflicts here
1434
                }
1435
            }
1436
 
1437
            // convert the dependencies into PEAR_Downloader_Package objects for the next time around
1438
            $params[$i]->_downloadDeps = array();
1439
            foreach ($newdeps as $dep) {
1440
                $obj = &new PEAR_Downloader_Package($params[$i]->getDownloader());
1441
                if ($s = $params[$i]->explicitState()) {
1442
                    $obj->setExplicitState($s);
1443
                }
1444
 
1445
                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1446
                $e = $obj->fromDepURL($dep);
1447
                PEAR::popErrorHandling();
1448
                if (PEAR::isError($e)) {
1449
                    if (!isset($options['soft'])) {
1450
                        $obj->_downloader->log(0, $e->getMessage());
1451
                    }
1452
                    continue;
1453
                }
1454
 
1455
                $e = $obj->detectDependencies($params);
1456
                if (PEAR::isError($e)) {
1457
                    if (!isset($options['soft'])) {
1458
                        $obj->_downloader->log(0, $e->getMessage());
1459
                    }
1460
                }
1461
 
1462
                $j = &$obj;
1463
                $newparams[] = &$j;
1464
            }
1465
        }
1466
 
1467
        if (count($newparams)) {
1468
            foreach ($newparams as $i => $unused) {
1469
                $params[] = &$newparams[$i];
1470
            }
1471
            return true;
1472
        }
1473
 
1474
        return false;
1475
    }
1476
 
1477
 
1478
    /**
1479
     * @static
1480
     */
1481
    function willDownload($param, $params)
1482
    {
1483
        if (!is_array($params)) {
1484
            return false;
1485
        }
1486
 
1487
        foreach ($params as $obj) {
1488
            if ($obj->isEqual($param)) {
1489
                return true;
1490
            }
1491
        }
1492
 
1493
        return false;
1494
    }
1495
 
1496
    /**
1497
     * For simpler unit-testing
1498
     * @param PEAR_Config
1499
     * @param int
1500
     * @param string
1501
     */
1502
    function &getPackagefileObject(&$c, $d)
1503
    {
1504
        $a = &new PEAR_PackageFile($c, $d);
1505
        return $a;
1506
    }
1507
 
1508
    /**
1509
     * This will retrieve from a local file if possible, and parse out
1510
     * a group name as well.  The original parameter will be modified to reflect this.
1511
     * @param string|array can be a parsed package name as well
1512
     * @access private
1513
     */
1514
    function _fromFile(&$param)
1515
    {
1516
        $saveparam = $param;
1517
        if (is_string($param)) {
1518
            if (!@file_exists($param)) {
1519
                $test = explode('#', $param);
1520
                $group = array_pop($test);
1521
                if (@file_exists(implode('#', $test))) {
1522
                    $this->setGroup($group);
1523
                    $param = implode('#', $test);
1524
                    $this->_explicitGroup = true;
1525
                }
1526
            }
1527
 
1528
            if (@is_file($param)) {
1529
                $this->_type = 'local';
1530
                $options = $this->_downloader->getOptions();
1531
                $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->_debug);
1532
                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1533
                $pf = &$pkg->fromAnyFile($param, PEAR_VALIDATE_INSTALLING);
1534
                PEAR::popErrorHandling();
1535
                if (PEAR::isError($pf)) {
1536
                    $this->_valid = false;
1537
                    $param = $saveparam;
1538
                    return $pf;
1539
                }
1540
                $this->_packagefile = &$pf;
1541
                if (!$this->getGroup()) {
1542
                    $this->setGroup('default'); // install the default dependency group
1543
                }
1544
                return $this->_valid = true;
1545
            }
1546
        }
1547
        $param = $saveparam;
1548
        return $this->_valid = false;
1549
    }
1550
 
1551
    function _fromUrl($param, $saveparam = '')
1552
    {
1553
        if (!is_array($param) && (preg_match('#^(http|https|ftp)://#', $param))) {
1554
            $options = $this->_downloader->getOptions();
1555
            $this->_type = 'url';
1556
            $callback = $this->_downloader->ui ?
1557
                array(&$this->_downloader, '_downloadCallback') : null;
1558
            $this->_downloader->pushErrorHandling(PEAR_ERROR_RETURN);
1559
            if (PEAR::isError($dir = $this->_downloader->getDownloadDir())) {
1560
                $this->_downloader->popErrorHandling();
1561
                return $dir;
1562
            }
1563
 
1564
            $this->_downloader->log(3, 'Downloading "' . $param . '"');
1565
            $file = $this->_downloader->downloadHttp($param, $this->_downloader->ui,
1566
                $dir, $callback, null, false, $this->getChannel());
1567
            $this->_downloader->popErrorHandling();
1568
            if (PEAR::isError($file)) {
1569
                if (!empty($saveparam)) {
1570
                    $saveparam = ", cannot download \"$saveparam\"";
1571
                }
1572
                $err = PEAR::raiseError('Could not download from "' . $param .
1573
                    '"' . $saveparam . ' (' . $file->getMessage() . ')');
1574
                    return $err;
1575
            }
1576
 
1577
            if ($this->_rawpackagefile) {
1578
                require_once 'Archive/Tar.php';
1579
                $tar = &new Archive_Tar($file);
1580
                $packagexml = $tar->extractInString('package2.xml');
1581
                if (!$packagexml) {
1582
                    $packagexml = $tar->extractInString('package.xml');
1583
                }
1584
 
1585
                if (str_replace(array("\n", "\r"), array('',''), $packagexml) !=
1586
                      str_replace(array("\n", "\r"), array('',''), $this->_rawpackagefile)) {
1587
                    if ($this->getChannel() != 'pear.php.net') {
1588
                        return PEAR::raiseError('CRITICAL ERROR: package.xml downloaded does ' .
1589
                            'not match value returned from xml-rpc');
1590
                    }
1591
 
1592
                    // be more lax for the existing PEAR packages that have not-ok
1593
                    // characters in their package.xml
1594
                    $this->_downloader->log(0, 'CRITICAL WARNING: The "' .
1595
                        $this->getPackage() . '" package has invalid characters in its ' .
1596
                        'package.xml.  The next version of PEAR may not be able to install ' .
1597
                        'this package for security reasons.  Please open a bug report at ' .
1598
                        'http://pear.php.net/package/' . $this->getPackage() . '/bugs');
1599
                }
1600
            }
1601
 
1602
            // whew, download worked!
1603
            $pkg = &$this->getPackagefileObject($this->_config, $this->_downloader->debug);
1604
 
1605
            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1606
            $pf = &$pkg->fromAnyFile($file, PEAR_VALIDATE_INSTALLING);
1607
            PEAR::popErrorHandling();
1608
            if (PEAR::isError($pf)) {
1609
                if (is_array($pf->getUserInfo())) {
1610
                    foreach ($pf->getUserInfo() as $err) {
1611
                        if (is_array($err)) {
1612
                            $err = $err['message'];
1613
                        }
1614
 
1615
                        if (!isset($options['soft'])) {
1616
                            $this->_downloader->log(0, "Validation Error: $err");
1617
                        }
1618
                    }
1619
                }
1620
 
1621
                if (!isset($options['soft'])) {
1622
                    $this->_downloader->log(0, $pf->getMessage());
1623
                }
1624
 
1625
                ///FIXME need to pass back some error code that we can use to match with to cancel all further operations
1626
                /// At least stop all deps of this package from being installed
1627
                $out = $saveparam ? $saveparam : $param;
1628
                $err = PEAR::raiseError('Download of "' . $out . '" succeeded, but it is not a valid package archive');
1629
                $this->_valid = false;
1630
                return $err;
1631
            }
1632
 
1633
            $this->_packagefile = &$pf;
1634
            $this->setGroup('default'); // install the default dependency group
1635
            return $this->_valid = true;
1636
        }
1637
 
1638
        return $this->_valid = false;
1639
    }
1640
 
1641
    /**
1642
     *
1643
     * @param string|array pass in an array of format
1644
     *                     array(
1645
     *                      'package' => 'pname',
1646
     *                     ['channel' => 'channame',]
1647
     *                     ['version' => 'version',]
1648
     *                     ['state' => 'state',])
1649
     *                     or a string of format [channame/]pname[-version|-state]
1650
     */
1651
    function _fromString($param)
1652
    {
1653
        $options = $this->_downloader->getOptions();
1654
        $channel = $this->_config->get('default_channel');
1655
        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1656
        $pname = $this->_registry->parsePackageName($param, $channel);
1657
        PEAR::popErrorHandling();
1658
        if (PEAR::isError($pname)) {
1659
            if ($pname->getCode() == 'invalid') {
1660
                $this->_valid = false;
1661
                return false;
1662
            }
1663
 
1664
            if ($pname->getCode() == 'channel') {
1665
                $parsed = $pname->getUserInfo();
1666
                if ($this->_downloader->discover($parsed['channel'])) {
1667
                    if ($this->_config->get('auto_discover')) {
1668
                        PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1669
                        $pname = $this->_registry->parsePackageName($param, $channel);
1670
                        PEAR::popErrorHandling();
1671
                    } else {
1672
                        if (!isset($options['soft'])) {
1673
                            $this->_downloader->log(0, 'Channel "' . $parsed['channel'] .
1674
                                '" is not initialized, use ' .
1675
                                '"pear channel-discover ' . $parsed['channel'] . '" to initialize' .
1676
                                'or pear config-set auto_discover 1');
1677
                        }
1678
                    }
1679
                }
1680
 
1681
                if (PEAR::isError($pname)) {
1682
                    if (!isset($options['soft'])) {
1683
                        $this->_downloader->log(0, $pname->getMessage());
1684
                    }
1685
 
1686
                    if (is_array($param)) {
1687
                        $param = $this->_registry->parsedPackageNameToString($param);
1688
                    }
1689
 
1690
                    $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
1691
                    $this->_valid = false;
1692
                    return $err;
1693
                }
1694
            } else {
1695
                if (!isset($options['soft'])) {
1696
                    $this->_downloader->log(0, $pname->getMessage());
1697
                }
1698
 
1699
                $err = PEAR::raiseError('invalid package name/package file "' . $param . '"');
1700
                $this->_valid = false;
1701
                return $err;
1702
            }
1703
        }
1704
 
1705
        if (!isset($this->_type)) {
1706
            $this->_type = 'rest';
1707
        }
1708
 
1709
        $this->_parsedname    = $pname;
1710
        $this->_explicitState = isset($pname['state']) ? $pname['state'] : false;
1711
        $this->_explicitGroup = isset($pname['group']) ? true : false;
1712
 
1713
        $info = $this->_downloader->_getPackageDownloadUrl($pname);
1714
        if (PEAR::isError($info)) {
1715
            if ($info->getCode() != -976 && $pname['channel'] == 'pear.php.net') {
1716
                // try pecl
1717
                $pname['channel'] = 'pecl.php.net';
1718
                if ($test = $this->_downloader->_getPackageDownloadUrl($pname)) {
1719
                    if (!PEAR::isError($test)) {
1720
                        $info = PEAR::raiseError($info->getMessage() . ' - package ' .
1721
                            $this->_registry->parsedPackageNameToString($pname, true) .
1722
                            ' can be installed with "pecl install ' . $pname['package'] .
1723
                            '"');
1724
                    } else {
1725
                        $pname['channel'] = 'pear.php.net';
1726
                    }
1727
                } else {
1728
                    $pname['channel'] = 'pear.php.net';
1729
                }
1730
            }
1731
 
1732
            return $info;
1733
        }
1734
 
1735
        $this->_rawpackagefile = $info['raw'];
1736
        $ret = $this->_analyzeDownloadURL($info, $param, $pname);
1737
        if (PEAR::isError($ret)) {
1738
            return $ret;
1739
        }
1740
 
1741
        if ($ret) {
1742
            $this->_downloadURL = $ret;
1743
            return $this->_valid = (bool) $ret;
1744
        }
1745
    }
1746
 
1747
    /**
1748
     * @param array output of package.getDownloadURL
1749
     * @param string|array|object information for detecting packages to be downloaded, and
1750
     *                            for errors
1751
     * @param array name information of the package
1752
     * @param array|null packages to be downloaded
1753
     * @param bool is this an optional dependency?
1754
     * @param bool is this any kind of dependency?
1755
     * @access private
1756
     */
1757
    function _analyzeDownloadURL($info, $param, $pname, $params = null, $optional = false,
1758
                                 $isdependency = false)
1759
    {
1760
        if (!is_string($param) && PEAR_Downloader_Package::willDownload($param, $params)) {
1761
            return false;
1762
        }
1763
 
1764
        if ($info === false) {
1765
            $saveparam = !is_string($param) ? ", cannot download \"$param\"" : '';
1766
 
1767
            // no releases exist
1768
            return PEAR::raiseError('No releases for package "' .
1769
                $this->_registry->parsedPackageNameToString($pname, true) . '" exist' . $saveparam);
1770
        }
1771
 
1772
        if (strtolower($info['info']->getChannel()) != strtolower($pname['channel'])) {
1773
            $err = false;
1774
            if ($pname['channel'] == 'pecl.php.net') {
1775
                if ($info['info']->getChannel() != 'pear.php.net') {
1776
                    $err = true;
1777
                }
1778
            } elseif ($info['info']->getChannel() == 'pecl.php.net') {
1779
                if ($pname['channel'] != 'pear.php.net') {
1780
                    $err = true;
1781
                }
1782
            } else {
1783
                $err = true;
1784
            }
1785
 
1786
            if ($err) {
1787
                return PEAR::raiseError('SECURITY ERROR: package in channel "' . $pname['channel'] .
1788
                    '" retrieved another channel\'s name for download! ("' .
1789
                    $info['info']->getChannel() . '")');
1790
            }
1791
        }
1792
 
1793
        $preferred_state = $this->_config->get('preferred_state');
1794
        if (!isset($info['url'])) {
1795
            $package_version = $this->_registry->packageInfo($info['info']->getPackage(),
1796
            'version', $info['info']->getChannel());
1797
            if ($this->isInstalled($info)) {
1798
                if ($isdependency && version_compare($info['version'], $package_version, '<=')) {
1799
                    // ignore bogus errors of "failed to download dependency"
1800
                    // if it is already installed and the one that would be
1801
                    // downloaded is older or the same version (Bug #7219)
1802
                    return false;
1803
                }
1804
            }
1805
 
1806
            if ($info['version'] === $package_version) {
1807
                if (!isset($options['soft'])) {
1808
                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
1809
                        '/' . $pname['package'] . '-' . $package_version. ', additionally the suggested version' .
1810
                        ' (' . $package_version . ') is the same as the locally installed one.');
1811
                }
1812
 
1813
                return false;
1814
            }
1815
 
1816
            if (version_compare($info['version'], $package_version, '<=')) {
1817
                if (!isset($options['soft'])) {
1818
                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
1819
                        '/' . $pname['package'] . '-' . $package_version . ', additionally the suggested version' .
1820
                        ' (' . $info['version'] . ') is a lower version than the locally installed one (' . $package_version . ').');
1821
                }
1822
 
1823
                return false;
1824
            }
1825
 
1826
            $instead =  ', will instead download version ' . $info['version'] .
1827
                        ', stability "' . $info['info']->getState() . '"';
1828
            // releases exist, but we failed to get any
1829
            if (isset($this->_downloader->_options['force'])) {
1830
                if (isset($pname['version'])) {
1831
                    $vs = ', version "' . $pname['version'] . '"';
1832
                } elseif (isset($pname['state'])) {
1833
                    $vs = ', stability "' . $pname['state'] . '"';
1834
                } elseif ($param == 'dependency') {
1835
                    if (!class_exists('PEAR_Common')) {
1836
                        require_once 'PEAR/Common.php';
1837
                    }
1838
 
1839
                    if (!in_array($info['info']->getState(),
1840
                          PEAR_Common::betterStates($preferred_state, true))) {
1841
                        if ($optional) {
1842
                            // don't spit out confusing error message
1843
                            return $this->_downloader->_getPackageDownloadUrl(
1844
                                array('package' => $pname['package'],
1845
                                      'channel' => $pname['channel'],
1846
                                      'version' => $info['version']));
1847
                        }
1848
                        $vs = ' within preferred state "' . $preferred_state .
1849
                            '"';
1850
                    } else {
1851
                        if (!class_exists('PEAR_Dependency2')) {
1852
                            require_once 'PEAR/Dependency2.php';
1853
                        }
1854
 
1855
                        if ($optional) {
1856
                            // don't spit out confusing error message
1857
                            return $this->_downloader->_getPackageDownloadUrl(
1858
                                array('package' => $pname['package'],
1859
                                      'channel' => $pname['channel'],
1860
                                      'version' => $info['version']));
1861
                        }
1862
                        $vs = PEAR_Dependency2::_getExtraString($pname);
1863
                        $instead = '';
1864
                    }
1865
                } else {
1866
                    $vs = ' within preferred state "' . $preferred_state . '"';
1867
                }
1868
 
1869
                if (!isset($options['soft'])) {
1870
                    $this->_downloader->log(1, 'WARNING: failed to download ' . $pname['channel'] .
1871
                        '/' . $pname['package'] . $vs . $instead);
1872
                }
1873
 
1874
                // download the latest release
1875
                return $this->_downloader->_getPackageDownloadUrl(
1876
                    array('package' => $pname['package'],
1877
                          'channel' => $pname['channel'],
1878
                          'version' => $info['version']));
1879
            } else {
1880
                if (isset($info['php']) && $info['php']) {
1881
                    $err = PEAR::raiseError('Failed to download ' .
1882
                        $this->_registry->parsedPackageNameToString(
1883
                            array('channel' => $pname['channel'],
1884
                                  'package' => $pname['package']),
1885
                                true) .
1886
                        ', latest release is version ' . $info['php']['v'] .
1887
                        ', but it requires PHP version "' .
1888
                        $info['php']['m'] . '", use "' .
1889
                        $this->_registry->parsedPackageNameToString(
1890
                            array('channel' => $pname['channel'], 'package' => $pname['package'],
1891
                            'version' => $info['php']['v'])) . '" to install',
1892
                            PEAR_DOWNLOADER_PACKAGE_PHPVERSION);
1893
                    return $err;
1894
                }
1895
 
1896
                // construct helpful error message
1897
                if (isset($pname['version'])) {
1898
                    $vs = ', version "' . $pname['version'] . '"';
1899
                } elseif (isset($pname['state'])) {
1900
                    $vs = ', stability "' . $pname['state'] . '"';
1901
                } elseif ($param == 'dependency') {
1902
                    if (!class_exists('PEAR_Common')) {
1903
                        require_once 'PEAR/Common.php';
1904
                    }
1905
 
1906
                    if (!in_array($info['info']->getState(),
1907
                          PEAR_Common::betterStates($preferred_state, true))) {
1908
                        if ($optional) {
1909
                            // don't spit out confusing error message, and don't die on
1910
                            // optional dep failure!
1911
                            return $this->_downloader->_getPackageDownloadUrl(
1912
                                array('package' => $pname['package'],
1913
                                      'channel' => $pname['channel'],
1914
                                      'version' => $info['version']));
1915
                        }
1916
                        $vs = ' within preferred state "' . $preferred_state . '"';
1917
                    } else {
1918
                        if (!class_exists('PEAR_Dependency2')) {
1919
                            require_once 'PEAR/Dependency2.php';
1920
                        }
1921
 
1922
                        if ($optional) {
1923
                            // don't spit out confusing error message, and don't die on
1924
                            // optional dep failure!
1925
                            return $this->_downloader->_getPackageDownloadUrl(
1926
                                array('package' => $pname['package'],
1927
                                      'channel' => $pname['channel'],
1928
                                      'version' => $info['version']));
1929
                        }
1930
                        $vs = PEAR_Dependency2::_getExtraString($pname);
1931
                    }
1932
                } else {
1933
                    $vs = ' within preferred state "' . $this->_downloader->config->get('preferred_state') . '"';
1934
                }
1935
 
1936
                $options = $this->_downloader->getOptions();
1937
                // this is only set by the "download-all" command
1938
                if (isset($options['ignorepreferred_state'])) {
1939
                    $err = PEAR::raiseError(
1940
                        'Failed to download ' . $this->_registry->parsedPackageNameToString(
1941
                            array('channel' => $pname['channel'], 'package' => $pname['package']),
1942
                                true)
1943
                         . $vs .
1944
                        ', latest release is version ' . $info['version'] .
1945
                        ', stability "' . $info['info']->getState() . '", use "' .
1946
                        $this->_registry->parsedPackageNameToString(
1947
                            array('channel' => $pname['channel'], 'package' => $pname['package'],
1948
                            'version' => $info['version'])) . '" to install',
1949
                            PEAR_DOWNLOADER_PACKAGE_STATE);
1950
                    return $err;
1951
                }
1952
 
1953
                // Checks if the user has a package installed already and checks the release against
1954
                // the state against the installed package, this allows upgrades for packages
1955
                // with lower stability than the preferred_state
1956
                $stability = $this->_registry->packageInfo($pname['package'], 'stability', $pname['channel']);
1957
                if (!$this->isInstalled($info)
1958
                    || !in_array($info['info']->getState(), PEAR_Common::betterStates($stability['release'], true))
1959
                ) {
1960
                    $err = PEAR::raiseError(
1961
                        'Failed to download ' . $this->_registry->parsedPackageNameToString(
1962
                            array('channel' => $pname['channel'], 'package' => $pname['package']),
1963
                                true)
1964
                         . $vs .
1965
                        ', latest release is version ' . $info['version'] .
1966
                        ', stability "' . $info['info']->getState() . '", use "' .
1967
                        $this->_registry->parsedPackageNameToString(
1968
                            array('channel' => $pname['channel'], 'package' => $pname['package'],
1969
                            'version' => $info['version'])) . '" to install');
1970
                    return $err;
1971
                }
1972
            }
1973
        }
1974
 
1975
        if (isset($info['deprecated']) && $info['deprecated']) {
1976
            $this->_downloader->log(0,
1977
                'WARNING: "' .
1978
                    $this->_registry->parsedPackageNameToString(
1979
                            array('channel' => $info['info']->getChannel(),
1980
                                  'package' => $info['info']->getPackage()), true) .
1981
                '" is deprecated in favor of "' .
1982
                    $this->_registry->parsedPackageNameToString($info['deprecated'], true) .
1983
                '"');
1984
        }
1985
 
1986
        return $info;
1987
    }
1988
}