Initial repo created
[timetracker.git] / WEB-INF / lib / pear / PEAR / Downloader / Package.php
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 }