Updated PEAR and PEAR packages.
[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  * @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  */
20 define('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  */
25 define('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: 1.10.1
53  * @link       http://pear.php.net/package/PEAR
54  * @since      Class available since Release 1.4.0a1
55  */
56 class 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)) {
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 }