Subversion Repositories Applications.gtt

Rev

Rev 94 | Details | Compare with Previous | Last modification | View Log | RSS feed

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