]> git.mxchange.org Git - friendica.git/blob - vendor/pear-pear.php.net/PEAR/PEAR/Downloader.php
Merge branch 'develop' into improvement/move-app-to-src-2
[friendica.git] / vendor / pear-pear.php.net / PEAR / PEAR / Downloader.php
1 <?php
2 /**
3  * PEAR_Downloader, the PEAR Installer's download utility class
4  *
5  * PHP versions 4 and 5
6  *
7  * @category   pear
8  * @package    PEAR
9  * @author     Greg Beaver <cellog@php.net>
10  * @author     Stig Bakken <ssb@php.net>
11  * @author     Tomas V. V. Cox <cox@idecnet.com>
12  * @author     Martin Jansen <mj@php.net>
13  * @copyright  1997-2009 The Authors
14  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
15  * @link       http://pear.php.net/package/PEAR
16  * @since      File available since Release 1.3.0
17  */
18
19 /**
20  * Needed for constants, extending
21  */
22 require_once 'PEAR/Common.php';
23 require_once 'PEAR/Proxy.php';
24
25 define('PEAR_INSTALLER_OK',       1);
26 define('PEAR_INSTALLER_FAILED',   0);
27 define('PEAR_INSTALLER_SKIPPED', -1);
28 define('PEAR_INSTALLER_ERROR_NO_PREF_STATE', 2);
29
30 /**
31  * Administration class used to download anything from the internet (PEAR Packages,
32  * static URLs, xml files)
33  *
34  * @category   pear
35  * @package    PEAR
36  * @author     Greg Beaver <cellog@php.net>
37  * @author     Stig Bakken <ssb@php.net>
38  * @author     Tomas V. V. Cox <cox@idecnet.com>
39  * @author     Martin Jansen <mj@php.net>
40  * @copyright  1997-2009 The Authors
41  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
42  * @version    Release: 1.10.4
43  * @link       http://pear.php.net/package/PEAR
44  * @since      Class available since Release 1.3.0
45  */
46 class PEAR_Downloader extends PEAR_Common
47 {
48     /**
49      * @var PEAR_Registry
50      * @access private
51      */
52     var $_registry;
53
54     /**
55      * Preferred Installation State (snapshot, devel, alpha, beta, stable)
56      * @var string|null
57      * @access private
58      */
59     var $_preferredState;
60
61     /**
62      * Options from command-line passed to Install.
63      *
64      * Recognized options:<br />
65      *  - onlyreqdeps   : install all required dependencies as well
66      *  - alldeps       : install all dependencies, including optional
67      *  - installroot   : base relative path to install files in
68      *  - force         : force a download even if warnings would prevent it
69      *  - nocompress    : download uncompressed tarballs
70      * @see PEAR_Command_Install
71      * @access private
72      * @var array
73      */
74     var $_options;
75
76     /**
77      * Downloaded Packages after a call to download().
78      *
79      * Format of each entry:
80      *
81      * <code>
82      * array('pkg' => 'package_name', 'file' => '/path/to/local/file',
83      *    'info' => array() // parsed package.xml
84      * );
85      * </code>
86      * @access private
87      * @var array
88      */
89     var $_downloadedPackages = array();
90
91     /**
92      * Packages slated for download.
93      *
94      * This is used to prevent downloading a package more than once should it be a dependency
95      * for two packages to be installed.
96      * Format of each entry:
97      *
98      * <pre>
99      * array('package_name1' => parsed package.xml, 'package_name2' => parsed package.xml,
100      * );
101      * </pre>
102      * @access private
103      * @var array
104      */
105     var $_toDownload = array();
106
107     /**
108      * Array of every package installed, with names lower-cased.
109      *
110      * Format:
111      * <code>
112      * array('package1' => 0, 'package2' => 1, );
113      * </code>
114      * @var array
115      */
116     var $_installed = array();
117
118     /**
119      * @var array
120      * @access private
121      */
122     var $_errorStack = array();
123
124     /**
125      * @var boolean
126      * @access private
127      */
128     var $_internalDownload = false;
129
130     /**
131      * Temporary variable used in sorting packages by dependency in {@link sortPkgDeps()}
132      * @var array
133      * @access private
134      */
135     var $_packageSortTree;
136
137     /**
138      * Temporary directory, or configuration value where downloads will occur
139      * @var string
140      */
141     var $_downloadDir;
142
143     /**
144      * List of methods that can be called both statically and non-statically.
145      * @var array
146      */
147     protected static $bivalentMethods = array(
148         'setErrorHandling' => true,
149         'raiseError' => true,
150         'throwError' => true,
151         'pushErrorHandling' => true,
152         'popErrorHandling' => true,
153         'downloadHttp' => true,
154     );
155
156     /**
157      * @param PEAR_Frontend_*
158      * @param array
159      * @param PEAR_Config
160      */
161     function __construct($ui = null, $options = array(), $config = null)
162     {
163         parent::__construct();
164         $this->_options = $options;
165         if ($config !== null) {
166             $this->config = &$config;
167             $this->_preferredState = $this->config->get('preferred_state');
168         }
169         $this->ui = &$ui;
170         if (!$this->_preferredState) {
171             // don't inadvertently use a non-set preferred_state
172             $this->_preferredState = null;
173         }
174
175         if ($config !== null) {
176             if (isset($this->_options['installroot'])) {
177                 $this->config->setInstallRoot($this->_options['installroot']);
178             }
179             $this->_registry = &$config->getRegistry();
180         }
181
182         if (isset($this->_options['alldeps']) || isset($this->_options['onlyreqdeps'])) {
183             $this->_installed = $this->_registry->listAllPackages();
184             foreach ($this->_installed as $key => $unused) {
185                 if (!count($unused)) {
186                     continue;
187                 }
188                 $strtolower = create_function('$a','return strtolower($a);');
189                 array_walk($this->_installed[$key], $strtolower);
190             }
191         }
192     }
193
194     /**
195      * Attempt to discover a channel's remote capabilities from
196      * its server name
197      * @param string
198      * @return boolean
199      */
200     function discover($channel)
201     {
202         $this->log(1, 'Attempting to discover channel "' . $channel . '"...');
203         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
204         $callback = $this->ui ? array(&$this, '_downloadCallback') : null;
205         if (!class_exists('System')) {
206             require_once 'System.php';
207         }
208
209         $tmpdir = $this->config->get('temp_dir');
210         $tmp = System::mktemp('-d -t "' . $tmpdir . '"');
211         $a   = $this->downloadHttp('http://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
212         PEAR::popErrorHandling();
213         if (PEAR::isError($a)) {
214             // Attempt to fallback to https automatically.
215             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
216             $this->log(1, 'Attempting fallback to https instead of http on channel "' . $channel . '"...');
217             $a = $this->downloadHttp('https://' . $channel . '/channel.xml', $this->ui, $tmp, $callback, false);
218             PEAR::popErrorHandling();
219             if (PEAR::isError($a)) {
220                 return false;
221             }
222         }
223
224         list($a, $lastmodified) = $a;
225         if (!class_exists('PEAR_ChannelFile')) {
226             require_once 'PEAR/ChannelFile.php';
227         }
228
229         $b = new PEAR_ChannelFile;
230         if ($b->fromXmlFile($a)) {
231             unlink($a);
232             if ($this->config->get('auto_discover')) {
233                 $this->_registry->addChannel($b, $lastmodified);
234                 $alias = $b->getName();
235                 if ($b->getName() == $this->_registry->channelName($b->getAlias())) {
236                     $alias = $b->getAlias();
237                 }
238
239                 $this->log(1, 'Auto-discovered channel "' . $channel .
240                     '", alias "' . $alias . '", adding to registry');
241             }
242
243             return true;
244         }
245
246         unlink($a);
247         return false;
248     }
249
250     /**
251      * For simpler unit-testing
252      * @param PEAR_Downloader
253      * @return PEAR_Downloader_Package
254      */
255     function newDownloaderPackage(&$t)
256     {
257         if (!class_exists('PEAR_Downloader_Package')) {
258             require_once 'PEAR/Downloader/Package.php';
259         }
260         $a = new PEAR_Downloader_Package($t);
261         return $a;
262     }
263
264     /**
265      * For simpler unit-testing
266      * @param PEAR_Config
267      * @param array
268      * @param array
269      * @param int
270      */
271     function &getDependency2Object(&$c, $i, $p, $s)
272     {
273         if (!class_exists('PEAR_Dependency2')) {
274             require_once 'PEAR/Dependency2.php';
275         }
276         $z = new PEAR_Dependency2($c, $i, $p, $s);
277         return $z;
278     }
279
280     function &download($params)
281     {
282         if (!count($params)) {
283             $a = array();
284             return $a;
285         }
286
287         if (!isset($this->_registry)) {
288             $this->_registry = &$this->config->getRegistry();
289         }
290
291         $channelschecked = array();
292         // convert all parameters into PEAR_Downloader_Package objects
293         foreach ($params as $i => $param) {
294             $params[$i] = $this->newDownloaderPackage($this);
295             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
296             $err = $params[$i]->initialize($param);
297             PEAR::staticPopErrorHandling();
298             if (!$err) {
299                 // skip parameters that were missed by preferred_state
300                 continue;
301             }
302
303             if (PEAR::isError($err)) {
304                 if (!isset($this->_options['soft']) && $err->getMessage() !== '') {
305                     $this->log(0, $err->getMessage());
306                 }
307
308                 $params[$i] = false;
309                 if (is_object($param)) {
310                     $param = $param->getChannel() . '/' . $param->getPackage();
311                 }
312
313                 if (!isset($this->_options['soft'])) {
314                     $this->log(2, 'Package "' . $param . '" is not valid');
315                 }
316
317                 // Message logged above in a specific verbose mode, passing null to not show up on CLI
318                 $this->pushError(null, PEAR_INSTALLER_SKIPPED);
319             } else {
320                 do {
321                     if ($params[$i] && $params[$i]->getType() == 'local') {
322                         // bug #7090 skip channel.xml check for local packages
323                         break;
324                     }
325
326                     if ($params[$i] && !isset($channelschecked[$params[$i]->getChannel()]) &&
327                           !isset($this->_options['offline'])
328                     ) {
329                         $channelschecked[$params[$i]->getChannel()] = true;
330                         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
331                         if (!class_exists('System')) {
332                             require_once 'System.php';
333                         }
334
335                         $curchannel = $this->_registry->getChannel($params[$i]->getChannel());
336                         if (PEAR::isError($curchannel)) {
337                             PEAR::staticPopErrorHandling();
338                             return $this->raiseError($curchannel);
339                         }
340
341                         if (PEAR::isError($dir = $this->getDownloadDir())) {
342                             PEAR::staticPopErrorHandling();
343                             break;
344                         }
345
346                         $mirror = $this->config->get('preferred_mirror', null, $params[$i]->getChannel());
347                         $url    = 'http://' . $mirror . '/channel.xml';
348                         $a = $this->downloadHttp($url, $this->ui, $dir, null, $curchannel->lastModified());
349
350                         PEAR::staticPopErrorHandling();
351                         if ($a === false) {
352                             //channel.xml not modified
353                             break;
354                         } else if (PEAR::isError($a)) {
355                             // Attempt fallback to https automatically
356                             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
357                             $a = $this->downloadHttp('https://' . $mirror .
358                                 '/channel.xml', $this->ui, $dir, null, $curchannel->lastModified());
359
360                             PEAR::staticPopErrorHandling();
361                             if (PEAR::isError($a) || !$a) {
362                                 break;
363                             }
364                         }
365                         $this->log(0, 'WARNING: channel "' . $params[$i]->getChannel() . '" has ' .
366                             'updated its protocols, use "' . PEAR_RUNTYPE . ' channel-update ' . $params[$i]->getChannel() .
367                             '" to update');
368                     }
369                 } while (false);
370
371                 if ($params[$i] && !isset($this->_options['downloadonly'])) {
372                     if (isset($this->_options['packagingroot'])) {
373                         $checkdir = $this->_prependPath(
374                             $this->config->get('php_dir', null, $params[$i]->getChannel()),
375                             $this->_options['packagingroot']);
376                     } else {
377                         $checkdir = $this->config->get('php_dir',
378                             null, $params[$i]->getChannel());
379                     }
380
381                     while ($checkdir && $checkdir != '/' && !file_exists($checkdir)) {
382                         $checkdir = dirname($checkdir);
383                     }
384
385                     if ($checkdir == '.') {
386                         $checkdir = '/';
387                     }
388
389                     if (!is_writeable($checkdir)) {
390                         return PEAR::raiseError('Cannot install, php_dir for channel "' .
391                             $params[$i]->getChannel() . '" is not writeable by the current user');
392                     }
393                 }
394             }
395         }
396
397         unset($channelschecked);
398         PEAR_Downloader_Package::removeDuplicates($params);
399         if (!count($params)) {
400             $a = array();
401             return $a;
402         }
403
404         if (!isset($this->_options['nodeps']) && !isset($this->_options['offline'])) {
405             $reverify = true;
406             while ($reverify) {
407                 $reverify = false;
408                 foreach ($params as $i => $param) {
409                     //PHP Bug 40768 / PEAR Bug #10944
410                     //Nested foreaches fail in PHP 5.2.1
411                     key($params);
412                     $ret = $params[$i]->detectDependencies($params);
413                     if (PEAR::isError($ret)) {
414                         $reverify = true;
415                         $params[$i] = false;
416                         PEAR_Downloader_Package::removeDuplicates($params);
417                         if (!isset($this->_options['soft'])) {
418                             $this->log(0, $ret->getMessage());
419                         }
420                         continue 2;
421                     }
422                 }
423             }
424         }
425
426         if (isset($this->_options['offline'])) {
427             $this->log(3, 'Skipping dependency download check, --offline specified');
428         }
429
430         if (!count($params)) {
431             $a = array();
432             return $a;
433         }
434
435         while (PEAR_Downloader_Package::mergeDependencies($params));
436         PEAR_Downloader_Package::removeDuplicates($params, true);
437         $errorparams = array();
438         if (PEAR_Downloader_Package::detectStupidDuplicates($params, $errorparams)) {
439             if (count($errorparams)) {
440                 foreach ($errorparams as $param) {
441                     $name = $this->_registry->parsedPackageNameToString($param->getParsedPackage());
442                     $this->pushError('Duplicate package ' . $name . ' found', PEAR_INSTALLER_FAILED);
443                 }
444                 $a = array();
445                 return $a;
446             }
447         }
448
449         PEAR_Downloader_Package::removeInstalled($params);
450         if (!count($params)) {
451             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
452             $a = array();
453             return $a;
454         }
455
456         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
457         $err = $this->analyzeDependencies($params);
458         PEAR::popErrorHandling();
459         if (!count($params)) {
460             $this->pushError('No valid packages found', PEAR_INSTALLER_FAILED);
461             $a = array();
462             return $a;
463         }
464
465         $ret = array();
466         $newparams = array();
467         if (isset($this->_options['pretend'])) {
468             return $params;
469         }
470
471         $somefailed = false;
472         foreach ($params as $i => $package) {
473             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
474             $pf = &$params[$i]->download();
475             PEAR::staticPopErrorHandling();
476             if (PEAR::isError($pf)) {
477                 if (!isset($this->_options['soft'])) {
478                     $this->log(1, $pf->getMessage());
479                     $this->log(0, 'Error: cannot download "' .
480                         $this->_registry->parsedPackageNameToString($package->getParsedPackage(),
481                             true) .
482                         '"');
483                 }
484                 $somefailed = true;
485                 continue;
486             }
487
488             $newparams[] = &$params[$i];
489             $ret[] = array(
490                 'file' => $pf->getArchiveFile(),
491                 'info' => &$pf,
492                 'pkg'  => $pf->getPackage()
493             );
494         }
495
496         if ($somefailed) {
497             // remove params that did not download successfully
498             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
499             $err = $this->analyzeDependencies($newparams, true);
500             PEAR::popErrorHandling();
501             if (!count($newparams)) {
502                 $this->pushError('Download failed', PEAR_INSTALLER_FAILED);
503                 $a = array();
504                 return $a;
505             }
506         }
507
508         $this->_downloadedPackages = $ret;
509         return $newparams;
510     }
511
512     /**
513      * @param array all packages to be installed
514      */
515     function analyzeDependencies(&$params, $force = false)
516     {
517         if (isset($this->_options['downloadonly'])) {
518             return;
519         }
520
521         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
522         $redo  = true;
523         $reset = $hasfailed = $failed = false;
524         while ($redo) {
525             $redo = false;
526             foreach ($params as $i => $param) {
527                 $deps = $param->getDeps();
528                 if (!$deps) {
529                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
530                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
531                     $send = $param->getPackageFile();
532
533                     $installcheck = $depchecker->validatePackage($send, $this, $params);
534                     if (PEAR::isError($installcheck)) {
535                         if (!isset($this->_options['soft'])) {
536                             $this->log(0, $installcheck->getMessage());
537                         }
538                         $hasfailed  = true;
539                         $params[$i] = false;
540                         $reset      = true;
541                         $redo       = true;
542                         $failed     = false;
543                         PEAR_Downloader_Package::removeDuplicates($params);
544                         continue 2;
545                     }
546                     continue;
547                 }
548
549                 if (!$reset && $param->alreadyValidated() && !$force) {
550                     continue;
551                 }
552
553                 if (count($deps)) {
554                     $depchecker = &$this->getDependency2Object($this->config, $this->getOptions(),
555                         $param->getParsedPackage(), PEAR_VALIDATE_DOWNLOADING);
556                     $send = $param->getPackageFile();
557                     if ($send === null) {
558                         $send = $param->getDownloadURL();
559                     }
560
561                     $installcheck = $depchecker->validatePackage($send, $this, $params);
562                     if (PEAR::isError($installcheck)) {
563                         if (!isset($this->_options['soft'])) {
564                             $this->log(0, $installcheck->getMessage());
565                         }
566                         $hasfailed  = true;
567                         $params[$i] = false;
568                         $reset      = true;
569                         $redo       = true;
570                         $failed     = false;
571                         PEAR_Downloader_Package::removeDuplicates($params);
572                         continue 2;
573                     }
574
575                     $failed = false;
576                     if (isset($deps['required']) && is_array($deps['required'])) {
577                         foreach ($deps['required'] as $type => $dep) {
578                             // note: Dependency2 will never return a PEAR_Error if ignore-errors
579                             // is specified, so soft is needed to turn off logging
580                             if (!isset($dep[0])) {
581                                 if (PEAR::isError($e = $depchecker->{"validate{$type}Dependency"}($dep,
582                                       true, $params))) {
583                                     $failed = true;
584                                     if (!isset($this->_options['soft'])) {
585                                         $this->log(0, $e->getMessage());
586                                     }
587                                 } elseif (is_array($e) && !$param->alreadyValidated()) {
588                                     if (!isset($this->_options['soft'])) {
589                                         $this->log(0, $e[0]);
590                                     }
591                                 }
592                             } else {
593                                 foreach ($dep as $d) {
594                                     if (PEAR::isError($e =
595                                           $depchecker->{"validate{$type}Dependency"}($d,
596                                           true, $params))) {
597                                         $failed = true;
598                                         if (!isset($this->_options['soft'])) {
599                                             $this->log(0, $e->getMessage());
600                                         }
601                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
602                                         if (!isset($this->_options['soft'])) {
603                                             $this->log(0, $e[0]);
604                                         }
605                                     }
606                                 }
607                             }
608                         }
609
610                         if (isset($deps['optional']) && is_array($deps['optional'])) {
611                             foreach ($deps['optional'] as $type => $dep) {
612                                 if (!isset($dep[0])) {
613                                     if (PEAR::isError($e =
614                                           $depchecker->{"validate{$type}Dependency"}($dep,
615                                           false, $params))) {
616                                         $failed = true;
617                                         if (!isset($this->_options['soft'])) {
618                                             $this->log(0, $e->getMessage());
619                                         }
620                                     } elseif (is_array($e) && !$param->alreadyValidated()) {
621                                         if (!isset($this->_options['soft'])) {
622                                             $this->log(0, $e[0]);
623                                         }
624                                     }
625                                 } else {
626                                     foreach ($dep as $d) {
627                                         if (PEAR::isError($e =
628                                               $depchecker->{"validate{$type}Dependency"}($d,
629                                               false, $params))) {
630                                             $failed = true;
631                                             if (!isset($this->_options['soft'])) {
632                                                 $this->log(0, $e->getMessage());
633                                             }
634                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
635                                             if (!isset($this->_options['soft'])) {
636                                                 $this->log(0, $e[0]);
637                                             }
638                                         }
639                                     }
640                                 }
641                             }
642                         }
643
644                         $groupname = $param->getGroup();
645                         if (isset($deps['group']) && $groupname) {
646                             if (!isset($deps['group'][0])) {
647                                 $deps['group'] = array($deps['group']);
648                             }
649
650                             $found = false;
651                             foreach ($deps['group'] as $group) {
652                                 if ($group['attribs']['name'] == $groupname) {
653                                     $found = true;
654                                     break;
655                                 }
656                             }
657
658                             if ($found) {
659                                 unset($group['attribs']);
660                                 foreach ($group as $type => $dep) {
661                                     if (!isset($dep[0])) {
662                                         if (PEAR::isError($e =
663                                               $depchecker->{"validate{$type}Dependency"}($dep,
664                                               false, $params))) {
665                                             $failed = true;
666                                             if (!isset($this->_options['soft'])) {
667                                                 $this->log(0, $e->getMessage());
668                                             }
669                                         } elseif (is_array($e) && !$param->alreadyValidated()) {
670                                             if (!isset($this->_options['soft'])) {
671                                                 $this->log(0, $e[0]);
672                                             }
673                                         }
674                                     } else {
675                                         foreach ($dep as $d) {
676                                             if (PEAR::isError($e =
677                                                   $depchecker->{"validate{$type}Dependency"}($d,
678                                                   false, $params))) {
679                                                 $failed = true;
680                                                 if (!isset($this->_options['soft'])) {
681                                                     $this->log(0, $e->getMessage());
682                                                 }
683                                             } elseif (is_array($e) && !$param->alreadyValidated()) {
684                                                 if (!isset($this->_options['soft'])) {
685                                                     $this->log(0, $e[0]);
686                                                 }
687                                             }
688                                         }
689                                     }
690                                 }
691                             }
692                         }
693                     } else {
694                         foreach ($deps as $dep) {
695                             if (PEAR::isError($e = $depchecker->validateDependency1($dep, $params))) {
696                                 $failed = true;
697                                 if (!isset($this->_options['soft'])) {
698                                     $this->log(0, $e->getMessage());
699                                 }
700                             } elseif (is_array($e) && !$param->alreadyValidated()) {
701                                 if (!isset($this->_options['soft'])) {
702                                     $this->log(0, $e[0]);
703                                 }
704                             }
705                         }
706                     }
707                     $params[$i]->setValidated();
708                 }
709
710                 if ($failed) {
711                     $hasfailed  = true;
712                     $params[$i] = false;
713                     $reset      = true;
714                     $redo       = true;
715                     $failed     = false;
716                     PEAR_Downloader_Package::removeDuplicates($params);
717                     continue 2;
718                 }
719             }
720         }
721
722         PEAR::staticPopErrorHandling();
723         if ($hasfailed && (isset($this->_options['ignore-errors']) ||
724               isset($this->_options['nodeps']))) {
725             // this is probably not needed, but just in case
726             if (!isset($this->_options['soft'])) {
727                 $this->log(0, 'WARNING: dependencies failed');
728             }
729         }
730     }
731
732     /**
733      * Retrieve the directory that downloads will happen in
734      * @access private
735      * @return string
736      */
737     function getDownloadDir()
738     {
739         if (isset($this->_downloadDir)) {
740             return $this->_downloadDir;
741         }
742
743         $downloaddir = $this->config->get('download_dir');
744         if (empty($downloaddir) || (is_dir($downloaddir) && !is_writable($downloaddir))) {
745             if  (is_dir($downloaddir) && !is_writable($downloaddir)) {
746                 $this->log(0, 'WARNING: configuration download directory "' . $downloaddir .
747                     '" is not writeable.  Change download_dir config variable to ' .
748                     'a writeable dir to avoid this warning');
749             }
750
751             if (!class_exists('System')) {
752                 require_once 'System.php';
753             }
754
755             if (PEAR::isError($downloaddir = System::mktemp('-d'))) {
756                 return $downloaddir;
757             }
758             $this->log(3, '+ tmp dir created at ' . $downloaddir);
759         }
760
761         if (!is_writable($downloaddir)) {
762             if (PEAR::isError(System::mkdir(array('-p', $downloaddir))) ||
763                   !is_writable($downloaddir)) {
764                 return PEAR::raiseError('download directory "' . $downloaddir .
765                     '" is not writeable.  Change download_dir config variable to ' .
766                     'a writeable dir');
767             }
768         }
769
770         return $this->_downloadDir = $downloaddir;
771     }
772
773     function setDownloadDir($dir)
774     {
775         if (!@is_writable($dir)) {
776             if (PEAR::isError(System::mkdir(array('-p', $dir)))) {
777                 return PEAR::raiseError('download directory "' . $dir .
778                     '" is not writeable.  Change download_dir config variable to ' .
779                     'a writeable dir');
780             }
781         }
782         $this->_downloadDir = $dir;
783     }
784
785     function configSet($key, $value, $layer = 'user', $channel = false)
786     {
787         $this->config->set($key, $value, $layer, $channel);
788         $this->_preferredState = $this->config->get('preferred_state', null, $channel);
789         if (!$this->_preferredState) {
790             // don't inadvertently use a non-set preferred_state
791             $this->_preferredState = null;
792         }
793     }
794
795     function setOptions($options)
796     {
797         $this->_options = $options;
798     }
799
800     function getOptions()
801     {
802         return $this->_options;
803     }
804
805
806     /**
807      * @param array output of {@link parsePackageName()}
808      * @access private
809      */
810     function _getPackageDownloadUrl($parr)
811     {
812         $curchannel = $this->config->get('default_channel');
813         $this->configSet('default_channel', $parr['channel']);
814         // getDownloadURL returns an array.  On error, it only contains information
815         // on the latest release as array(version, info).  On success it contains
816         // array(version, info, download url string)
817         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
818         if (!$this->_registry->channelExists($parr['channel'])) {
819             do {
820                 if ($this->config->get('auto_discover') && $this->discover($parr['channel'])) {
821                     break;
822                 }
823
824                 $this->configSet('default_channel', $curchannel);
825                 return PEAR::raiseError('Unknown remote channel: ' . $parr['channel']);
826             } while (false);
827         }
828
829         $chan = $this->_registry->getChannel($parr['channel']);
830         if (PEAR::isError($chan)) {
831             return $chan;
832         }
833
834         PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
835         $version   = $this->_registry->packageInfo($parr['package'], 'version', $parr['channel']);
836         $stability = $this->_registry->packageInfo($parr['package'], 'stability', $parr['channel']);
837         // package is installed - use the installed release stability level
838         if (!isset($parr['state']) && $stability !== null) {
839             $state = $stability['release'];
840         }
841         PEAR::staticPopErrorHandling();
842         $base2 = false;
843
844         $preferred_mirror = $this->config->get('preferred_mirror');
845         if (!$chan->supportsREST($preferred_mirror) ||
846               (
847                !($base2 = $chan->getBaseURL('REST1.3', $preferred_mirror))
848                &&
849                !($base = $chan->getBaseURL('REST1.0', $preferred_mirror))
850               )
851         ) {
852             return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
853         }
854
855         if ($base2) {
856             $rest = &$this->config->getREST('1.3', $this->_options);
857             $base = $base2;
858         } else {
859             $rest = &$this->config->getREST('1.0', $this->_options);
860         }
861
862         $downloadVersion = false;
863         if (!isset($parr['version']) && !isset($parr['state']) && $version
864               && !PEAR::isError($version)
865               && !isset($this->_options['downloadonly'])
866         ) {
867             $downloadVersion = $version;
868         }
869
870         $url = $rest->getDownloadURL($base, $parr, $state, $downloadVersion, $chan->getName());
871         if (PEAR::isError($url)) {
872             $this->configSet('default_channel', $curchannel);
873             return $url;
874         }
875
876         if ($parr['channel'] != $curchannel) {
877             $this->configSet('default_channel', $curchannel);
878         }
879
880         if (!is_array($url)) {
881             return $url;
882         }
883
884         $url['raw'] = false; // no checking is necessary for REST
885         if (!is_array($url['info'])) {
886             return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
887                 'this should never happen');
888         }
889
890         if (!isset($this->_options['force']) &&
891               !isset($this->_options['downloadonly']) &&
892               $version &&
893               !PEAR::isError($version) &&
894               !isset($parr['group'])
895         ) {
896             if (version_compare($version, $url['version'], '=')) {
897                 return PEAR::raiseError($this->_registry->parsedPackageNameToString(
898                     $parr, true) . ' is already installed and is the same as the ' .
899                     'released version ' . $url['version'], -976);
900             }
901
902             if (version_compare($version, $url['version'], '>')) {
903                 return PEAR::raiseError($this->_registry->parsedPackageNameToString(
904                     $parr, true) . ' is already installed and is newer than detected ' .
905                     'released version ' . $url['version'], -976);
906             }
907         }
908
909         if (isset($url['info']['required']) || $url['compatible']) {
910             require_once 'PEAR/PackageFile/v2.php';
911             $pf = new PEAR_PackageFile_v2;
912             $pf->setRawChannel($parr['channel']);
913             if ($url['compatible']) {
914                 $pf->setRawCompatible($url['compatible']);
915             }
916         } else {
917             require_once 'PEAR/PackageFile/v1.php';
918             $pf = new PEAR_PackageFile_v1;
919         }
920
921         $pf->setRawPackage($url['package']);
922         $pf->setDeps($url['info']);
923         if ($url['compatible']) {
924             $pf->setCompatible($url['compatible']);
925         }
926
927         $pf->setRawState($url['stability']);
928         $url['info'] = &$pf;
929         if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
930             $ext = '.tar';
931         } else {
932             $ext = '.tgz';
933         }
934
935         if (is_array($url) && isset($url['url'])) {
936             $url['url'] .= $ext;
937         }
938
939         return $url;
940     }
941
942     /**
943      * @param array dependency array
944      * @access private
945      */
946     function _getDepPackageDownloadUrl($dep, $parr)
947     {
948         $xsdversion = isset($dep['rel']) ? '1.0' : '2.0';
949         $curchannel = $this->config->get('default_channel');
950         if (isset($dep['uri'])) {
951             $xsdversion = '2.0';
952             $chan = $this->_registry->getChannel('__uri');
953             if (PEAR::isError($chan)) {
954                 return $chan;
955             }
956
957             $version = $this->_registry->packageInfo($dep['name'], 'version', '__uri');
958             $this->configSet('default_channel', '__uri');
959         } else {
960             if (isset($dep['channel'])) {
961                 $remotechannel = $dep['channel'];
962             } else {
963                 $remotechannel = 'pear.php.net';
964             }
965
966             if (!$this->_registry->channelExists($remotechannel)) {
967                 do {
968                     if ($this->config->get('auto_discover')) {
969                         if ($this->discover($remotechannel)) {
970                             break;
971                         }
972                     }
973                     return PEAR::raiseError('Unknown remote channel: ' . $remotechannel);
974                 } while (false);
975             }
976
977             $chan = $this->_registry->getChannel($remotechannel);
978             if (PEAR::isError($chan)) {
979                 return $chan;
980             }
981
982             $version = $this->_registry->packageInfo($dep['name'], 'version', $remotechannel);
983             $this->configSet('default_channel', $remotechannel);
984         }
985
986         $state = isset($parr['state']) ? $parr['state'] : $this->config->get('preferred_state');
987         if (isset($parr['state']) && isset($parr['version'])) {
988             unset($parr['state']);
989         }
990
991         if (isset($dep['uri'])) {
992             $info = $this->newDownloaderPackage($this);
993             PEAR::staticPushErrorHandling(PEAR_ERROR_RETURN);
994             $err = $info->initialize($dep);
995             PEAR::staticPopErrorHandling();
996             if (!$err) {
997                 // skip parameters that were missed by preferred_state
998                 return PEAR::raiseError('Cannot initialize dependency');
999             }
1000
1001             if (PEAR::isError($err)) {
1002                 if (!isset($this->_options['soft'])) {
1003                     $this->log(0, $err->getMessage());
1004                 }
1005
1006                 if (is_object($info)) {
1007                     $param = $info->getChannel() . '/' . $info->getPackage();
1008                 }
1009                 return PEAR::raiseError('Package "' . $param . '" is not valid');
1010             }
1011             return $info;
1012         } elseif ($chan->supportsREST($this->config->get('preferred_mirror'))
1013               &&
1014                 (
1015                   ($base2 = $chan->getBaseURL('REST1.3', $this->config->get('preferred_mirror')))
1016                     ||
1017                   ($base = $chan->getBaseURL('REST1.0', $this->config->get('preferred_mirror')))
1018                 )
1019         ) {
1020             if ($base2) {
1021                 $base = $base2;
1022                 $rest = &$this->config->getREST('1.3', $this->_options);
1023             } else {
1024                 $rest = &$this->config->getREST('1.0', $this->_options);
1025             }
1026
1027             $url = $rest->getDepDownloadURL($base, $xsdversion, $dep, $parr,
1028                     $state, $version, $chan->getName());
1029             if (PEAR::isError($url)) {
1030                 return $url;
1031             }
1032
1033             if ($parr['channel'] != $curchannel) {
1034                 $this->configSet('default_channel', $curchannel);
1035             }
1036
1037             if (!is_array($url)) {
1038                 return $url;
1039             }
1040
1041             $url['raw'] = false; // no checking is necessary for REST
1042             if (!is_array($url['info'])) {
1043                 return PEAR::raiseError('Invalid remote dependencies retrieved from REST - ' .
1044                     'this should never happen');
1045             }
1046
1047             if (isset($url['info']['required'])) {
1048                 if (!class_exists('PEAR_PackageFile_v2')) {
1049                     require_once 'PEAR/PackageFile/v2.php';
1050                 }
1051                 $pf = new PEAR_PackageFile_v2;
1052                 $pf->setRawChannel($remotechannel);
1053             } else {
1054                 if (!class_exists('PEAR_PackageFile_v1')) {
1055                     require_once 'PEAR/PackageFile/v1.php';
1056                 }
1057                 $pf = new PEAR_PackageFile_v1;
1058
1059             }
1060             $pf->setRawPackage($url['package']);
1061             $pf->setDeps($url['info']);
1062             if ($url['compatible']) {
1063                 $pf->setCompatible($url['compatible']);
1064             }
1065
1066             $pf->setRawState($url['stability']);
1067             $url['info'] = &$pf;
1068             if (!extension_loaded("zlib") || isset($this->_options['nocompress'])) {
1069                 $ext = '.tar';
1070             } else {
1071                 $ext = '.tgz';
1072             }
1073
1074             if (is_array($url) && isset($url['url'])) {
1075                 $url['url'] .= $ext;
1076             }
1077
1078             return $url;
1079         }
1080
1081         return $this->raiseError($parr['channel'] . ' is using a unsupported protocol - This should never happen.');
1082     }
1083
1084     /**
1085      * @deprecated in favor of _getPackageDownloadUrl
1086      */
1087     function getPackageDownloadUrl($package, $version = null, $channel = false)
1088     {
1089         if ($version) {
1090             $package .= "-$version";
1091         }
1092         if ($this === null || $this->_registry === null) {
1093             $package = "http://pear.php.net/get/$package";
1094         } else {
1095             $chan = $this->_registry->getChannel($channel);
1096             if (PEAR::isError($chan)) {
1097                 return '';
1098             }
1099             $package = "http://" . $chan->getServer() . "/get/$package";
1100         }
1101         if (!extension_loaded("zlib")) {
1102             $package .= '?uncompress=yes';
1103         }
1104         return $package;
1105     }
1106
1107     /**
1108      * Retrieve a list of downloaded packages after a call to {@link download()}.
1109      *
1110      * Also resets the list of downloaded packages.
1111      * @return array
1112      */
1113     function getDownloadedPackages()
1114     {
1115         $ret = $this->_downloadedPackages;
1116         $this->_downloadedPackages = array();
1117         $this->_toDownload = array();
1118         return $ret;
1119     }
1120
1121     function _downloadCallback($msg, $params = null)
1122     {
1123         switch ($msg) {
1124             case 'saveas':
1125                 $this->log(1, "downloading $params ...");
1126                 break;
1127             case 'done':
1128                 $this->log(1, '...done: ' . number_format($params, 0, '', ',') . ' bytes');
1129                 break;
1130             case 'bytesread':
1131                 static $bytes;
1132                 if (empty($bytes)) {
1133                     $bytes = 0;
1134                 }
1135                 if (!($bytes % 10240)) {
1136                     $this->log(1, '.', false);
1137                 }
1138                 $bytes += $params;
1139                 break;
1140             case 'start':
1141                 if($params[1] == -1) {
1142                     $length = "Unknown size";
1143                 } else {
1144                     $length = number_format($params[1], 0, '', ',')." bytes";
1145                 }
1146                 $this->log(1, "Starting to download {$params[0]} ($length)");
1147                 break;
1148         }
1149         if (method_exists($this->ui, '_downloadCallback'))
1150             $this->ui->_downloadCallback($msg, $params);
1151     }
1152
1153     function _prependPath($path, $prepend)
1154     {
1155         if (strlen($prepend) > 0) {
1156             if (OS_WINDOWS && preg_match('/^[a-z]:/i', $path)) {
1157                 if (preg_match('/^[a-z]:/i', $prepend)) {
1158                     $prepend = substr($prepend, 2);
1159                 } elseif ($prepend{0} != '\\') {
1160                     $prepend = "\\$prepend";
1161                 }
1162                 $path = substr($path, 0, 2) . $prepend . substr($path, 2);
1163             } else {
1164                 $path = $prepend . $path;
1165             }
1166         }
1167         return $path;
1168     }
1169
1170     /**
1171      * @param string
1172      * @param integer
1173      */
1174     function pushError($errmsg, $code = -1)
1175     {
1176         array_push($this->_errorStack, array($errmsg, $code));
1177     }
1178
1179     function getErrorMsgs()
1180     {
1181         $msgs = array();
1182         $errs = $this->_errorStack;
1183         foreach ($errs as $err) {
1184             $msgs[] = $err[0];
1185         }
1186         $this->_errorStack = array();
1187         return $msgs;
1188     }
1189
1190     /**
1191      * for BC
1192      *
1193      * @deprecated
1194      */
1195     function sortPkgDeps(&$packages, $uninstall = false)
1196     {
1197         $uninstall ?
1198             $this->sortPackagesForUninstall($packages) :
1199             $this->sortPackagesForInstall($packages);
1200     }
1201
1202     /**
1203      * Sort a list of arrays of array(downloaded packagefilename) by dependency.
1204      *
1205      * This uses the topological sort method from graph theory, and the
1206      * Structures_Graph package to properly sort dependencies for installation.
1207      * @param array an array of downloaded PEAR_Downloader_Packages
1208      * @return array array of array(packagefilename, package.xml contents)
1209      */
1210     function sortPackagesForInstall(&$packages)
1211     {
1212         require_once 'Structures/Graph.php';
1213         require_once 'Structures/Graph/Node.php';
1214         require_once 'Structures/Graph/Manipulator/TopologicalSorter.php';
1215         $depgraph = new Structures_Graph(true);
1216         $nodes = array();
1217         $reg = &$this->config->getRegistry();
1218         foreach ($packages as $i => $package) {
1219             $pname = $reg->parsedPackageNameToString(
1220                 array(
1221                     'channel' => $package->getChannel(),
1222                     'package' => strtolower($package->getPackage()),
1223                 ));
1224             $nodes[$pname] = new Structures_Graph_Node;
1225             $nodes[$pname]->setData($packages[$i]);
1226             $depgraph->addNode($nodes[$pname]);
1227         }
1228
1229         $deplinks = array();
1230         foreach ($nodes as $package => $node) {
1231             $pf = &$node->getData();
1232             $pdeps = $pf->getDeps(true);
1233             if (!$pdeps) {
1234                 continue;
1235             }
1236
1237             if ($pf->getPackagexmlVersion() == '1.0') {
1238                 foreach ($pdeps as $dep) {
1239                     if ($dep['type'] != 'pkg' ||
1240                           (isset($dep['optional']) && $dep['optional'] == 'yes')) {
1241                         continue;
1242                     }
1243
1244                     $dname = $reg->parsedPackageNameToString(
1245                           array(
1246                               'channel' => 'pear.php.net',
1247                               'package' => strtolower($dep['name']),
1248                           ));
1249
1250                     if (isset($nodes[$dname])) {
1251                         if (!isset($deplinks[$dname])) {
1252                             $deplinks[$dname] = array();
1253                         }
1254
1255                         $deplinks[$dname][$package] = 1;
1256                         // dependency is in installed packages
1257                         continue;
1258                     }
1259
1260                     $dname = $reg->parsedPackageNameToString(
1261                           array(
1262                               'channel' => 'pecl.php.net',
1263                               'package' => strtolower($dep['name']),
1264                           ));
1265
1266                     if (isset($nodes[$dname])) {
1267                         if (!isset($deplinks[$dname])) {
1268                             $deplinks[$dname] = array();
1269                         }
1270
1271                         $deplinks[$dname][$package] = 1;
1272                         // dependency is in installed packages
1273                         continue;
1274                     }
1275                 }
1276             } else {
1277                 // the only ordering we care about is:
1278                 // 1) subpackages must be installed before packages that depend on them
1279                 // 2) required deps must be installed before packages that depend on them
1280                 if (isset($pdeps['required']['subpackage'])) {
1281                     $t = $pdeps['required']['subpackage'];
1282                     if (!isset($t[0])) {
1283                         $t = array($t);
1284                     }
1285
1286                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
1287                 }
1288
1289                 if (isset($pdeps['group'])) {
1290                     if (!isset($pdeps['group'][0])) {
1291                         $pdeps['group'] = array($pdeps['group']);
1292                     }
1293
1294                     foreach ($pdeps['group'] as $group) {
1295                         if (isset($group['subpackage'])) {
1296                             $t = $group['subpackage'];
1297                             if (!isset($t[0])) {
1298                                 $t = array($t);
1299                             }
1300
1301                             $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
1302                         }
1303                     }
1304                 }
1305
1306                 if (isset($pdeps['optional']['subpackage'])) {
1307                     $t = $pdeps['optional']['subpackage'];
1308                     if (!isset($t[0])) {
1309                         $t = array($t);
1310                     }
1311
1312                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
1313                 }
1314
1315                 if (isset($pdeps['required']['package'])) {
1316                     $t = $pdeps['required']['package'];
1317                     if (!isset($t[0])) {
1318                         $t = array($t);
1319                     }
1320
1321                     $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
1322                 }
1323
1324                 if (isset($pdeps['group'])) {
1325                     if (!isset($pdeps['group'][0])) {
1326                         $pdeps['group'] = array($pdeps['group']);
1327                     }
1328
1329                     foreach ($pdeps['group'] as $group) {
1330                         if (isset($group['package'])) {
1331                             $t = $group['package'];
1332                             if (!isset($t[0])) {
1333                                 $t = array($t);
1334                             }
1335
1336                             $this->_setupGraph($t, $reg, $deplinks, $nodes, $package);
1337                         }
1338                     }
1339                 }
1340             }
1341         }
1342
1343         $this->_detectDepCycle($deplinks);
1344         foreach ($deplinks as $dependent => $parents) {
1345             foreach ($parents as $parent => $unused) {
1346                 $nodes[$dependent]->connectTo($nodes[$parent]);
1347             }
1348         }
1349
1350         $installOrder = Structures_Graph_Manipulator_TopologicalSorter::sort($depgraph);
1351         $ret = array();
1352         for ($i = 0, $count = count($installOrder); $i < $count; $i++) {
1353             foreach ($installOrder[$i] as $index => $sortedpackage) {
1354                 $data = &$installOrder[$i][$index]->getData();
1355                 $ret[] = &$nodes[$reg->parsedPackageNameToString(
1356                           array(
1357                               'channel' => $data->getChannel(),
1358                               'package' => strtolower($data->getPackage()),
1359                           ))]->getData();
1360             }
1361         }
1362
1363         $packages = $ret;
1364         return;
1365     }
1366
1367     /**
1368      * Detect recursive links between dependencies and break the cycles
1369      *
1370      * @param array
1371      * @access private
1372      */
1373     function _detectDepCycle(&$deplinks)
1374     {
1375         do {
1376             $keepgoing = false;
1377             foreach ($deplinks as $dep => $parents) {
1378                 foreach ($parents as $parent => $unused) {
1379                     // reset the parent cycle detector
1380                     $this->_testCycle(null, null, null);
1381                     if ($this->_testCycle($dep, $deplinks, $parent)) {
1382                         $keepgoing = true;
1383                         unset($deplinks[$dep][$parent]);
1384                         if (count($deplinks[$dep]) == 0) {
1385                             unset($deplinks[$dep]);
1386                         }
1387
1388                         continue 3;
1389                     }
1390                 }
1391             }
1392         } while ($keepgoing);
1393     }
1394
1395     function _testCycle($test, $deplinks, $dep)
1396     {
1397         static $visited = array();
1398         if ($test === null) {
1399             $visited = array();
1400             return;
1401         }
1402
1403         // this happens when a parent has a dep cycle on another dependency
1404         // but the child is not part of the cycle
1405         if (isset($visited[$dep])) {
1406             return false;
1407         }
1408
1409         $visited[$dep] = 1;
1410         if ($test == $dep) {
1411             return true;
1412         }
1413
1414         if (isset($deplinks[$dep])) {
1415             if (in_array($test, array_keys($deplinks[$dep]), true)) {
1416                 return true;
1417             }
1418
1419             foreach ($deplinks[$dep] as $parent => $unused) {
1420                 if ($this->_testCycle($test, $deplinks, $parent)) {
1421                     return true;
1422                 }
1423             }
1424         }
1425
1426         return false;
1427     }
1428
1429     /**
1430      * Set up the dependency for installation parsing
1431      *
1432      * @param array $t dependency information
1433      * @param PEAR_Registry $reg
1434      * @param array $deplinks list of dependency links already established
1435      * @param array $nodes all existing package nodes
1436      * @param string $package parent package name
1437      * @access private
1438      */
1439     function _setupGraph($t, $reg, &$deplinks, &$nodes, $package)
1440     {
1441         foreach ($t as $dep) {
1442             $depchannel = !isset($dep['channel']) ? '__uri': $dep['channel'];
1443             $dname = $reg->parsedPackageNameToString(
1444                   array(
1445                       'channel' => $depchannel,
1446                       'package' => strtolower($dep['name']),
1447                   ));
1448
1449             if (isset($nodes[$dname])) {
1450                 if (!isset($deplinks[$dname])) {
1451                     $deplinks[$dname] = array();
1452                 }
1453                 $deplinks[$dname][$package] = 1;
1454             }
1455         }
1456     }
1457
1458     function _dependsOn($a, $b)
1459     {
1460         return $this->_checkDepTree(strtolower($a->getChannel()), strtolower($a->getPackage()), $b);
1461     }
1462
1463     function _checkDepTree($channel, $package, $b, $checked = array())
1464     {
1465         $checked[$channel][$package] = true;
1466         if (!isset($this->_depTree[$channel][$package])) {
1467             return false;
1468         }
1469
1470         if (isset($this->_depTree[$channel][$package][strtolower($b->getChannel())]
1471               [strtolower($b->getPackage())])) {
1472             return true;
1473         }
1474
1475         foreach ($this->_depTree[$channel][$package] as $ch => $packages) {
1476             foreach ($packages as $pa => $true) {
1477                 if ($this->_checkDepTree($ch, $pa, $b, $checked)) {
1478                     return true;
1479                 }
1480             }
1481         }
1482
1483         return false;
1484     }
1485
1486     function _sortInstall($a, $b)
1487     {
1488         if (!$a->getDeps() && !$b->getDeps()) {
1489             return 0; // neither package has dependencies, order is insignificant
1490         }
1491         if ($a->getDeps() && !$b->getDeps()) {
1492             return 1; // $a must be installed after $b because $a has dependencies
1493         }
1494         if (!$a->getDeps() && $b->getDeps()) {
1495             return -1; // $b must be installed after $a because $b has dependencies
1496         }
1497         // both packages have dependencies
1498         if ($this->_dependsOn($a, $b)) {
1499             return 1;
1500         }
1501         if ($this->_dependsOn($b, $a)) {
1502             return -1;
1503         }
1504         return 0;
1505     }
1506
1507     /**
1508      * Download a file through HTTP.  Considers suggested file name in
1509      * Content-disposition: header and can run a callback function for
1510      * different events.  The callback will be called with two
1511      * parameters: the callback type, and parameters.  The implemented
1512      * callback types are:
1513      *
1514      *  'setup'       called at the very beginning, parameter is a UI object
1515      *                that should be used for all output
1516      *  'message'     the parameter is a string with an informational message
1517      *  'saveas'      may be used to save with a different file name, the
1518      *                parameter is the filename that is about to be used.
1519      *                If a 'saveas' callback returns a non-empty string,
1520      *                that file name will be used as the filename instead.
1521      *                Note that $save_dir will not be affected by this, only
1522      *                the basename of the file.
1523      *  'start'       download is starting, parameter is number of bytes
1524      *                that are expected, or -1 if unknown
1525      *  'bytesread'   parameter is the number of bytes read so far
1526      *  'done'        download is complete, parameter is the total number
1527      *                of bytes read
1528      *  'connfailed'  if the TCP/SSL connection fails, this callback is called
1529      *                with array(host,port,errno,errmsg)
1530      *  'writefailed' if writing to disk fails, this callback is called
1531      *                with array(destfile,errmsg)
1532      *
1533      * If an HTTP proxy has been configured (http_proxy PEAR_Config
1534      * setting), the proxy will be used.
1535      *
1536      * @param string  $url       the URL to download
1537      * @param object  $ui        PEAR_Frontend_* instance
1538      * @param object  $config    PEAR_Config instance
1539      * @param string  $save_dir  directory to save file in
1540      * @param mixed   $callback  function/method to call for status
1541      *                           updates
1542      * @param false|string|array $lastmodified header values to check against for caching
1543      *                           use false to return the header values from this download
1544      * @param false|array $accept Accept headers to send
1545      * @param false|string $channel Channel to use for retrieving authentication
1546      * @return mixed  Returns the full path of the downloaded file or a PEAR
1547      *                error on failure.  If the error is caused by
1548      *                socket-related errors, the error object will
1549      *                have the fsockopen error code available through
1550      *                getCode().  If caching is requested, then return the header
1551      *                values.
1552      *                If $lastmodified was given and the there are no changes,
1553      *                boolean false is returned.
1554      *
1555      * @access public
1556      */
1557     public static function _downloadHttp(
1558         $object, $url, &$ui, $save_dir = '.', $callback = null, $lastmodified = null,
1559         $accept = false, $channel = false
1560     ) {
1561         static $redirect = 0;
1562         // always reset , so we are clean case of error
1563         $wasredirect = $redirect;
1564         $redirect = 0;
1565         if ($callback) {
1566             call_user_func($callback, 'setup', array(&$ui));
1567         }
1568
1569         $info = parse_url($url);
1570         if (!isset($info['scheme']) || !in_array($info['scheme'], array('http', 'https'))) {
1571             return PEAR::raiseError('Cannot download non-http URL "' . $url . '"');
1572         }
1573
1574         if (!isset($info['host'])) {
1575             return PEAR::raiseError('Cannot download from non-URL "' . $url . '"');
1576         }
1577
1578         $host = isset($info['host']) ? $info['host'] : null;
1579         $port = isset($info['port']) ? $info['port'] : null;
1580         $path = isset($info['path']) ? $info['path'] : null;
1581
1582         if ($object !== null) {
1583             $config = $object->config;
1584         } else {
1585             $config = &PEAR_Config::singleton();
1586         }
1587
1588         $proxy = new PEAR_Proxy($config);
1589
1590         if ($proxy->isProxyConfigured() && $callback) {
1591             call_user_func($callback, 'message', "Using HTTP proxy $host:$port");
1592         }
1593
1594         if (empty($port)) {
1595             $port = (isset($info['scheme']) && $info['scheme'] == 'https') ? 443 : 80;
1596         }
1597
1598         $scheme = (isset($info['scheme']) && $info['scheme'] == 'https') ? 'https' : 'http';
1599         $secure = ($scheme == 'https');
1600
1601         $fp = $proxy->openSocket($host, $port, $secure);
1602         if (PEAR::isError($fp)) {
1603             if ($callback) {
1604                 $errno = $fp->getCode();
1605                 $errstr = $fp->getMessage();
1606                 call_user_func($callback, 'connfailed', array($host, $port,
1607                                                               $errno, $errstr));
1608             }
1609             return $fp;
1610         }
1611
1612         $requestPath = $path;
1613         if ($proxy->isProxyConfigured()) {
1614             $requestPath = $url;
1615         }
1616
1617         if ($lastmodified === false || $lastmodified) {
1618             $request  = "GET $requestPath HTTP/1.1\r\n";
1619         } else {
1620             $request  = "GET $requestPath HTTP/1.0\r\n";
1621         }
1622         $request .= "Host: $host\r\n";
1623
1624         $ifmodifiedsince = '';
1625         if (is_array($lastmodified)) {
1626             if (isset($lastmodified['Last-Modified'])) {
1627                 $ifmodifiedsince = 'If-Modified-Since: ' . $lastmodified['Last-Modified'] . "\r\n";
1628             }
1629
1630             if (isset($lastmodified['ETag'])) {
1631                 $ifmodifiedsince .= "If-None-Match: $lastmodified[ETag]\r\n";
1632             }
1633         } else {
1634             $ifmodifiedsince = ($lastmodified ? "If-Modified-Since: $lastmodified\r\n" : '');
1635         }
1636
1637         $request .= $ifmodifiedsince .
1638             "User-Agent: PEAR/1.10.4/PHP/" . PHP_VERSION . "\r\n";
1639
1640         if ($object !== null) { // only pass in authentication for non-static calls
1641             $username = $config->get('username', null, $channel);
1642             $password = $config->get('password', null, $channel);
1643             if ($username && $password) {
1644                 $tmp = base64_encode("$username:$password");
1645                 $request .= "Authorization: Basic $tmp\r\n";
1646             }
1647         }
1648
1649         $proxyAuth = $proxy->getProxyAuth();
1650         if ($proxyAuth) {
1651             $request .= 'Proxy-Authorization: Basic ' .
1652                 $proxyAuth . "\r\n";
1653         }
1654
1655         if ($accept) {
1656             $request .= 'Accept: ' . implode(', ', $accept) . "\r\n";
1657         }
1658
1659         $request .= "Connection: close\r\n";
1660         $request .= "\r\n";
1661         fwrite($fp, $request);
1662         $headers = array();
1663         $reply = 0;
1664         while (trim($line = fgets($fp, 1024))) {
1665             if (preg_match('/^([^:]+):\s+(.*)\s*\\z/', $line, $matches)) {
1666                 $headers[strtolower($matches[1])] = trim($matches[2]);
1667             } elseif (preg_match('|^HTTP/1.[01] ([0-9]{3}) |', $line, $matches)) {
1668                 $reply = (int)$matches[1];
1669                 if ($reply == 304 && ($lastmodified || ($lastmodified === false))) {
1670                     return false;
1671                 }
1672
1673                 if (!in_array($reply, array(200, 301, 302, 303, 305, 307))) {
1674                     return PEAR::raiseError("File $scheme://$host:$port$path not valid (received: $line)");
1675                 }
1676             }
1677         }
1678
1679         if ($reply != 200) {
1680             if (!isset($headers['location'])) {
1681                 return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirected but no location)");
1682             }
1683
1684             if ($wasredirect > 4) {
1685                 return PEAR::raiseError("File $scheme://$host:$port$path not valid (redirection looped more than 5 times)");
1686             }
1687
1688             $redirect = $wasredirect + 1;
1689             return static::_downloadHttp($object, $headers['location'],
1690                     $ui, $save_dir, $callback, $lastmodified, $accept);
1691         }
1692
1693         if (isset($headers['content-disposition']) &&
1694             preg_match('/\sfilename=\"([^;]*\S)\"\s*(;|\\z)/', $headers['content-disposition'], $matches)) {
1695             $save_as = basename($matches[1]);
1696         } else {
1697             $save_as = basename($url);
1698         }
1699
1700         if ($callback) {
1701             $tmp = call_user_func($callback, 'saveas', $save_as);
1702             if ($tmp) {
1703                 $save_as = $tmp;
1704             }
1705         }
1706
1707         $dest_file = $save_dir . DIRECTORY_SEPARATOR . $save_as;
1708         if (is_link($dest_file)) {
1709             return PEAR::raiseError('SECURITY ERROR: Will not write to ' . $dest_file . ' as it is symlinked to ' . readlink($dest_file) . ' - Possible symlink attack');
1710         }
1711
1712         if (!$wp = @fopen($dest_file, 'wb')) {
1713             fclose($fp);
1714             if ($callback) {
1715                 call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
1716             }
1717             return PEAR::raiseError("could not open $dest_file for writing");
1718         }
1719
1720         $length = isset($headers['content-length']) ? $headers['content-length'] : -1;
1721
1722         $bytes = 0;
1723         if ($callback) {
1724             call_user_func($callback, 'start', array(basename($dest_file), $length));
1725         }
1726
1727         while ($data = fread($fp, 1024)) {
1728             $bytes += strlen($data);
1729             if ($callback) {
1730                 call_user_func($callback, 'bytesread', $bytes);
1731             }
1732             if (!@fwrite($wp, $data)) {
1733                 fclose($fp);
1734                 if ($callback) {
1735                     call_user_func($callback, 'writefailed', array($dest_file, $php_errormsg));
1736                 }
1737                 return PEAR::raiseError("$dest_file: write failed ($php_errormsg)");
1738             }
1739         }
1740
1741         fclose($fp);
1742         fclose($wp);
1743         if ($callback) {
1744             call_user_func($callback, 'done', $bytes);
1745         }
1746
1747         if ($lastmodified === false || $lastmodified) {
1748             if (isset($headers['etag'])) {
1749                 $lastmodified = array('ETag' => $headers['etag']);
1750             }
1751
1752             if (isset($headers['last-modified'])) {
1753                 if (is_array($lastmodified)) {
1754                     $lastmodified['Last-Modified'] = $headers['last-modified'];
1755                 } else {
1756                     $lastmodified = $headers['last-modified'];
1757                 }
1758             }
1759             return array($dest_file, $lastmodified, $headers);
1760         }
1761         return $dest_file;
1762     }
1763 }