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