Initial repo created
[timetracker.git] / WEB-INF / lib / pear / PEAR / REST / 10.php
1 <?php
2 /**
3  * PEAR_REST_10
4  *
5  * PHP versions 4 and 5
6  *
7  * @category   pear
8  * @package    PEAR
9  * @author     Greg Beaver <cellog@php.net>
10  * @copyright  1997-2009 The Authors
11  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12  * @version    CVS: $Id: 10.php 313023 2011-07-06 19:17:11Z dufuz $
13  * @link       http://pear.php.net/package/PEAR
14  * @since      File available since Release 1.4.0a12
15  */
16
17 /**
18  * For downloading REST xml/txt files
19  */
20 require_once 'PEAR/REST.php';
21
22 /**
23  * Implement REST 1.0
24  *
25  * @category   pear
26  * @package    PEAR
27  * @author     Greg Beaver <cellog@php.net>
28  * @copyright  1997-2009 The Authors
29  * @license    http://opensource.org/licenses/bsd-license.php New BSD License
30  * @version    Release: 1.9.4
31  * @link       http://pear.php.net/package/PEAR
32  * @since      Class available since Release 1.4.0a12
33  */
34 class PEAR_REST_10
35 {
36     /**
37      * @var PEAR_REST
38      */
39     var $_rest;
40     function PEAR_REST_10($config, $options = array())
41     {
42         $this->_rest = &new PEAR_REST($config, $options);
43     }
44
45     /**
46      * Retrieve information about a remote package to be downloaded from a REST server
47      *
48      * @param string $base The uri to prepend to all REST calls
49      * @param array $packageinfo an array of format:
50      * <pre>
51      *  array(
52      *   'package' => 'packagename',
53      *   'channel' => 'channelname',
54      *  ['state' => 'alpha' (or valid state),]
55      *  -or-
56      *  ['version' => '1.whatever']
57      * </pre>
58      * @param string $prefstate Current preferred_state config variable value
59      * @param bool $installed the installed version of this package to compare against
60      * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
61      */
62     function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
63     {
64         $states = $this->betterStates($prefstate, true);
65         if (!$states) {
66             return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
67         }
68
69         $channel  = $packageinfo['channel'];
70         $package  = $packageinfo['package'];
71         $state    = isset($packageinfo['state'])   ? $packageinfo['state']   : null;
72         $version  = isset($packageinfo['version']) ? $packageinfo['version'] : null;
73         $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
74
75         $info = $this->_rest->retrieveData($restFile, false, false, $channel);
76         if (PEAR::isError($info)) {
77             return PEAR::raiseError('No releases available for package "' .
78                 $channel . '/' . $package . '"');
79         }
80
81         if (!isset($info['r'])) {
82             return false;
83         }
84
85         $release = $found = false;
86         if (!is_array($info['r']) || !isset($info['r'][0])) {
87             $info['r'] = array($info['r']);
88         }
89
90         foreach ($info['r'] as $release) {
91             if (!isset($this->_rest->_options['force']) && ($installed &&
92                   version_compare($release['v'], $installed, '<'))) {
93                 continue;
94             }
95
96             if (isset($state)) {
97                 // try our preferred state first
98                 if ($release['s'] == $state) {
99                     $found = true;
100                     break;
101                 }
102                 // see if there is something newer and more stable
103                 // bug #7221
104                 if (in_array($release['s'], $this->betterStates($state), true)) {
105                     $found = true;
106                     break;
107                 }
108             } elseif (isset($version)) {
109                 if ($release['v'] == $version) {
110                     $found = true;
111                     break;
112                 }
113             } else {
114                 if (in_array($release['s'], $states)) {
115                     $found = true;
116                     break;
117                 }
118             }
119         }
120
121         return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
122     }
123
124     function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
125                                $prefstate = 'stable', $installed = false, $channel = false)
126     {
127         $states = $this->betterStates($prefstate, true);
128         if (!$states) {
129             return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
130         }
131
132         $channel  = $dependency['channel'];
133         $package  = $dependency['name'];
134         $state    = isset($dependency['state'])   ? $dependency['state']   : null;
135         $version  = isset($dependency['version']) ? $dependency['version'] : null;
136         $restFile = $base . 'r/' . strtolower($package) . '/allreleases.xml';
137
138         $info = $this->_rest->retrieveData($restFile, false, false, $channel);
139         if (PEAR::isError($info)) {
140             return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
141                 . '" dependency "' . $channel . '/' . $package . '" has no releases');
142         }
143
144         if (!is_array($info) || !isset($info['r'])) {
145             return false;
146         }
147
148         $exclude = array();
149         $min = $max = $recommended = false;
150         if ($xsdversion == '1.0') {
151             switch ($dependency['rel']) {
152                 case 'ge' :
153                     $min = $dependency['version'];
154                 break;
155                 case 'gt' :
156                     $min = $dependency['version'];
157                     $exclude = array($dependency['version']);
158                 break;
159                 case 'eq' :
160                     $recommended = $dependency['version'];
161                 break;
162                 case 'lt' :
163                     $max = $dependency['version'];
164                     $exclude = array($dependency['version']);
165                 break;
166                 case 'le' :
167                     $max = $dependency['version'];
168                 break;
169                 case 'ne' :
170                     $exclude = array($dependency['version']);
171                 break;
172             }
173         } else {
174             $min = isset($dependency['min']) ? $dependency['min'] : false;
175             $max = isset($dependency['max']) ? $dependency['max'] : false;
176             $recommended = isset($dependency['recommended']) ?
177                 $dependency['recommended'] : false;
178             if (isset($dependency['exclude'])) {
179                 if (!isset($dependency['exclude'][0])) {
180                     $exclude = array($dependency['exclude']);
181                 }
182             }
183         }
184         $release = $found = false;
185         if (!is_array($info['r']) || !isset($info['r'][0])) {
186             $info['r'] = array($info['r']);
187         }
188         foreach ($info['r'] as $release) {
189             if (!isset($this->_rest->_options['force']) && ($installed &&
190                   version_compare($release['v'], $installed, '<'))) {
191                 continue;
192             }
193             if (in_array($release['v'], $exclude)) { // skip excluded versions
194                 continue;
195             }
196             // allow newer releases to say "I'm OK with the dependent package"
197             if ($xsdversion == '2.0' && isset($release['co'])) {
198                 if (!is_array($release['co']) || !isset($release['co'][0])) {
199                     $release['co'] = array($release['co']);
200                 }
201                 foreach ($release['co'] as $entry) {
202                     if (isset($entry['x']) && !is_array($entry['x'])) {
203                         $entry['x'] = array($entry['x']);
204                     } elseif (!isset($entry['x'])) {
205                         $entry['x'] = array();
206                     }
207                     if ($entry['c'] == $deppackage['channel'] &&
208                           strtolower($entry['p']) == strtolower($deppackage['package']) &&
209                           version_compare($deppackage['version'], $entry['min'], '>=') &&
210                           version_compare($deppackage['version'], $entry['max'], '<=') &&
211                           !in_array($release['v'], $entry['x'])) {
212                         $recommended = $release['v'];
213                         break;
214                     }
215                 }
216             }
217             if ($recommended) {
218                 if ($release['v'] != $recommended) { // if we want a specific
219                     // version, then skip all others
220                     continue;
221                 } else {
222                     if (!in_array($release['s'], $states)) {
223                         // the stability is too low, but we must return the
224                         // recommended version if possible
225                         return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
226                     }
227                 }
228             }
229             if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
230                 continue;
231             }
232             if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
233                 continue;
234             }
235             if ($installed && version_compare($release['v'], $installed, '<')) {
236                 continue;
237             }
238             if (in_array($release['s'], $states)) { // if in the preferred state...
239                 $found = true; // ... then use it
240                 break;
241             }
242         }
243         return $this->_returnDownloadURL($base, $package, $release, $info, $found, false, $channel);
244     }
245
246     /**
247      * Take raw data and return the array needed for processing a download URL
248      *
249      * @param string $base REST base uri
250      * @param string $package Package name
251      * @param array $release an array of format array('v' => version, 's' => state)
252      *                       describing the release to download
253      * @param array $info list of all releases as defined by allreleases.xml
254      * @param bool|null $found determines whether the release was found or this is the next
255      *                    best alternative.  If null, then versions were skipped because
256      *                    of PHP dependency
257      * @return array|PEAR_Error
258      * @access private
259      */
260     function _returnDownloadURL($base, $package, $release, $info, $found, $phpversion = false, $channel = false)
261     {
262         if (!$found) {
263             $release = $info['r'][0];
264         }
265
266         $packageLower = strtolower($package);
267         $pinfo = $this->_rest->retrieveCacheFirst($base . 'p/' . $packageLower . '/' .
268             'info.xml', false, false, $channel);
269         if (PEAR::isError($pinfo)) {
270             return PEAR::raiseError('Package "' . $package .
271                 '" does not have REST info xml available');
272         }
273
274         $releaseinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
275             $release['v'] . '.xml', false, false, $channel);
276         if (PEAR::isError($releaseinfo)) {
277             return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
278                 '" does not have REST xml available');
279         }
280
281         $packagexml = $this->_rest->retrieveCacheFirst($base . 'r/' . $packageLower . '/' .
282             'deps.' . $release['v'] . '.txt', false, true, $channel);
283         if (PEAR::isError($packagexml)) {
284             return PEAR::raiseError('Package "' . $package . '" Version "' . $release['v'] .
285                 '" does not have REST dependency information available');
286         }
287
288         $packagexml = unserialize($packagexml);
289         if (!$packagexml) {
290             $packagexml = array();
291         }
292
293         $allinfo = $this->_rest->retrieveData($base . 'r/' . $packageLower .
294             '/allreleases.xml', false, false, $channel);
295         if (PEAR::isError($allinfo)) {
296             return $allinfo;
297         }
298
299         if (!is_array($allinfo['r']) || !isset($allinfo['r'][0])) {
300             $allinfo['r'] = array($allinfo['r']);
301         }
302
303         $compatible = false;
304         foreach ($allinfo['r'] as $release) {
305             if ($release['v'] != $releaseinfo['v']) {
306                 continue;
307             }
308
309             if (!isset($release['co'])) {
310                 break;
311             }
312
313             $compatible = array();
314             if (!is_array($release['co']) || !isset($release['co'][0])) {
315                 $release['co'] = array($release['co']);
316             }
317
318             foreach ($release['co'] as $entry) {
319                 $comp = array();
320                 $comp['name']    = $entry['p'];
321                 $comp['channel'] = $entry['c'];
322                 $comp['min']     = $entry['min'];
323                 $comp['max']     = $entry['max'];
324                 if (isset($entry['x']) && !is_array($entry['x'])) {
325                     $comp['exclude'] = $entry['x'];
326                 }
327
328                 $compatible[] = $comp;
329             }
330
331             if (count($compatible) == 1) {
332                 $compatible = $compatible[0];
333             }
334
335             break;
336         }
337
338         $deprecated = false;
339         if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
340             if (is_array($pinfo['dp'])) {
341                 $deprecated = array('channel' => (string) $pinfo['dc'],
342                                     'package' => trim($pinfo['dp']['_content']));
343             } else {
344                 $deprecated = array('channel' => (string) $pinfo['dc'],
345                                     'package' => trim($pinfo['dp']));
346             }
347         }
348
349         $return = array(
350             'version'    => $releaseinfo['v'],
351             'info'       => $packagexml,
352             'package'    => $releaseinfo['p']['_content'],
353             'stability'  => $releaseinfo['st'],
354             'compatible' => $compatible,
355             'deprecated' => $deprecated,
356         );
357
358         if ($found) {
359             $return['url'] = $releaseinfo['g'];
360             return $return;
361         }
362
363         $return['php'] = $phpversion;
364         return $return;
365     }
366
367     function listPackages($base, $channel = false)
368     {
369         $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
370         if (PEAR::isError($packagelist)) {
371             return $packagelist;
372         }
373
374         if (!is_array($packagelist) || !isset($packagelist['p'])) {
375             return array();
376         }
377
378         if (!is_array($packagelist['p'])) {
379             $packagelist['p'] = array($packagelist['p']);
380         }
381
382         return $packagelist['p'];
383     }
384
385     /**
386      * List all categories of a REST server
387      *
388      * @param string $base base URL of the server
389      * @return array of categorynames
390      */
391     function listCategories($base, $channel = false)
392     {
393         $categories = array();
394
395         // c/categories.xml does not exist;
396         // check for every package its category manually
397         // This is SLOOOWWWW : ///
398         $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
399         if (PEAR::isError($packagelist)) {
400             return $packagelist;
401         }
402
403         if (!is_array($packagelist) || !isset($packagelist['p'])) {
404             $ret = array();
405             return $ret;
406         }
407
408         if (!is_array($packagelist['p'])) {
409             $packagelist['p'] = array($packagelist['p']);
410         }
411
412         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
413         foreach ($packagelist['p'] as $package) {
414                 $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
415                 if (PEAR::isError($inf)) {
416                     PEAR::popErrorHandling();
417                     return $inf;
418                 }
419                 $cat = $inf['ca']['_content'];
420                 if (!isset($categories[$cat])) {
421                     $categories[$cat] = $inf['ca'];
422                 }
423         }
424
425         return array_values($categories);
426     }
427
428     /**
429      * List a category of a REST server
430      *
431      * @param string $base base URL of the server
432      * @param string $category name of the category
433      * @param boolean $info also download full package info
434      * @return array of packagenames
435      */
436     function listCategory($base, $category, $info = false, $channel = false)
437     {
438         // gives '404 Not Found' error when category doesn't exist
439         $packagelist = $this->_rest->retrieveData($base.'c/'.urlencode($category).'/packages.xml', false, false, $channel);
440         if (PEAR::isError($packagelist)) {
441             return $packagelist;
442         }
443
444         if (!is_array($packagelist) || !isset($packagelist['p'])) {
445             return array();
446         }
447
448         if (!is_array($packagelist['p']) ||
449             !isset($packagelist['p'][0])) { // only 1 pkg
450             $packagelist = array($packagelist['p']);
451         } else {
452             $packagelist = $packagelist['p'];
453         }
454
455         if ($info == true) {
456             // get individual package info
457             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
458             foreach ($packagelist as $i => $packageitem) {
459                 $url = sprintf('%s'.'r/%s/latest.txt',
460                         $base,
461                         strtolower($packageitem['_content']));
462                 $version = $this->_rest->retrieveData($url, false, false, $channel);
463                 if (PEAR::isError($version)) {
464                     break; // skipit
465                 }
466                 $url = sprintf('%s'.'r/%s/%s.xml',
467                         $base,
468                         strtolower($packageitem['_content']),
469                         $version);
470                 $info = $this->_rest->retrieveData($url, false, false, $channel);
471                 if (PEAR::isError($info)) {
472                     break; // skipit
473                 }
474                 $packagelist[$i]['info'] = $info;
475             }
476             PEAR::popErrorHandling();
477         }
478
479         return $packagelist;
480     }
481
482
483     function listAll($base, $dostable, $basic = true, $searchpackage = false, $searchsummary = false, $channel = false)
484     {
485         $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
486         if (PEAR::isError($packagelist)) {
487             return $packagelist;
488         }
489         if ($this->_rest->config->get('verbose') > 0) {
490             $ui = &PEAR_Frontend::singleton();
491             $ui->log('Retrieving data...0%', true);
492         }
493         $ret = array();
494         if (!is_array($packagelist) || !isset($packagelist['p'])) {
495             return $ret;
496         }
497         if (!is_array($packagelist['p'])) {
498             $packagelist['p'] = array($packagelist['p']);
499         }
500
501         // only search-packagename = quicksearch !
502         if ($searchpackage && (!$searchsummary || empty($searchpackage))) {
503             $newpackagelist = array();
504             foreach ($packagelist['p'] as $package) {
505                 if (!empty($searchpackage) && stristr($package, $searchpackage) !== false) {
506                     $newpackagelist[] = $package;
507                 }
508             }
509             $packagelist['p'] = $newpackagelist;
510         }
511         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
512         $next = .1;
513         foreach ($packagelist['p'] as $progress => $package) {
514             if ($this->_rest->config->get('verbose') > 0) {
515                 if ($progress / count($packagelist['p']) >= $next) {
516                     if ($next == .5) {
517                         $ui->log('50%', false);
518                     } else {
519                         $ui->log('.', false);
520                     }
521                     $next += .1;
522                 }
523             }
524
525             if ($basic) { // remote-list command
526                 if ($dostable) {
527                     $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
528                         '/stable.txt', false, false, $channel);
529                 } else {
530                     $latest = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
531                         '/latest.txt', false, false, $channel);
532                 }
533                 if (PEAR::isError($latest)) {
534                     $latest = false;
535                 }
536                 $info = array('stable' => $latest);
537             } else { // list-all command
538                 $inf = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
539                 if (PEAR::isError($inf)) {
540                     PEAR::popErrorHandling();
541                     return $inf;
542                 }
543                 if ($searchpackage) {
544                     $found = (!empty($searchpackage) && stristr($package, $searchpackage) !== false);
545                     if (!$found && !(isset($searchsummary) && !empty($searchsummary)
546                         && (stristr($inf['s'], $searchsummary) !== false
547                             || stristr($inf['d'], $searchsummary) !== false)))
548                     {
549                         continue;
550                     };
551                 }
552                 $releases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
553                     '/allreleases.xml', false, false, $channel);
554                 if (PEAR::isError($releases)) {
555                     continue;
556                 }
557                 if (!isset($releases['r'][0])) {
558                     $releases['r'] = array($releases['r']);
559                 }
560                 unset($latest);
561                 unset($unstable);
562                 unset($stable);
563                 unset($state);
564                 foreach ($releases['r'] as $release) {
565                     if (!isset($latest)) {
566                         if ($dostable && $release['s'] == 'stable') {
567                             $latest = $release['v'];
568                             $state = 'stable';
569                         }
570                         if (!$dostable) {
571                             $latest = $release['v'];
572                             $state = $release['s'];
573                         }
574                     }
575                     if (!isset($stable) && $release['s'] == 'stable') {
576                         $stable = $release['v'];
577                         if (!isset($unstable)) {
578                             $unstable = $stable;
579                         }
580                     }
581                     if (!isset($unstable) && $release['s'] != 'stable') {
582                         $latest = $unstable = $release['v'];
583                         $state = $release['s'];
584                     }
585                     if (isset($latest) && !isset($state)) {
586                         $state = $release['s'];
587                     }
588                     if (isset($latest) && isset($stable) && isset($unstable)) {
589                         break;
590                     }
591                 }
592                 $deps = array();
593                 if (!isset($unstable)) {
594                     $unstable = false;
595                     $state = 'stable';
596                     if (isset($stable)) {
597                         $latest = $unstable = $stable;
598                     }
599                 } else {
600                     $latest = $unstable;
601                 }
602                 if (!isset($latest)) {
603                     $latest = false;
604                 }
605                 if ($latest) {
606                     $d = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
607                         $latest . '.txt', false, false, $channel);
608                     if (!PEAR::isError($d)) {
609                         $d = unserialize($d);
610                         if ($d) {
611                             if (isset($d['required'])) {
612                                 if (!class_exists('PEAR_PackageFile_v2')) {
613                                     require_once 'PEAR/PackageFile/v2.php';
614                                 }
615                                 if (!isset($pf)) {
616                                     $pf = new PEAR_PackageFile_v2;
617                                 }
618                                 $pf->setDeps($d);
619                                 $tdeps = $pf->getDeps();
620                             } else {
621                                 $tdeps = $d;
622                             }
623                             foreach ($tdeps as $dep) {
624                                 if ($dep['type'] !== 'pkg') {
625                                     continue;
626                                 }
627                                 $deps[] = $dep;
628                             }
629                         }
630                     }
631                 }
632                 if (!isset($stable)) {
633                     $stable = '-n/a-';
634                 }
635                 if (!$searchpackage) {
636                     $info = array('stable' => $latest, 'summary' => $inf['s'], 'description' =>
637                         $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
638                         'unstable' => $unstable, 'state' => $state);
639                 } else {
640                     $info = array('stable' => $stable, 'summary' => $inf['s'], 'description' =>
641                         $inf['d'], 'deps' => $deps, 'category' => $inf['ca']['_content'],
642                         'unstable' => $unstable, 'state' => $state);
643                 }
644             }
645             $ret[$package] = $info;
646         }
647         PEAR::popErrorHandling();
648         return $ret;
649     }
650
651     function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg)
652     {
653         $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
654         if (PEAR::isError($packagelist)) {
655             return $packagelist;
656         }
657
658         $ret = array();
659         if (!is_array($packagelist) || !isset($packagelist['p'])) {
660             return $ret;
661         }
662
663         if (!is_array($packagelist['p'])) {
664             $packagelist['p'] = array($packagelist['p']);
665         }
666
667         foreach ($packagelist['p'] as $package) {
668             if (!isset($installed[strtolower($package)])) {
669                 continue;
670             }
671
672             $inst_version = $reg->packageInfo($package, 'version', $channel);
673             $inst_state   = $reg->packageInfo($package, 'release_state', $channel);
674             PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
675             $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
676                 '/allreleases.xml', false, false, $channel);
677             PEAR::popErrorHandling();
678             if (PEAR::isError($info)) {
679                 continue; // no remote releases
680             }
681
682             if (!isset($info['r'])) {
683                 continue;
684             }
685
686             $release = $found = false;
687             if (!is_array($info['r']) || !isset($info['r'][0])) {
688                 $info['r'] = array($info['r']);
689             }
690
691             // $info['r'] is sorted by version number
692             usort($info['r'], array($this, '_sortReleasesByVersionNumber'));
693             foreach ($info['r'] as $release) {
694                 if ($inst_version && version_compare($release['v'], $inst_version, '<=')) {
695                     // not newer than the one installed
696                     break;
697                 }
698
699                 // new version > installed version
700                 if (!$pref_state) {
701                     // every state is a good state
702                     $found = true;
703                     break;
704                 } else {
705                     $new_state = $release['s'];
706                     // if new state >= installed state: go
707                     if (in_array($new_state, $this->betterStates($inst_state, true))) {
708                         $found = true;
709                         break;
710                     } else {
711                         // only allow to lower the state of package,
712                         // if new state >= preferred state: go
713                         if (in_array($new_state, $this->betterStates($pref_state, true))) {
714                             $found = true;
715                             break;
716                         }
717                     }
718                 }
719             }
720
721             if (!$found) {
722                 continue;
723             }
724
725             $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' .
726                 $release['v'] . '.xml', false, false, $channel);
727             if (PEAR::isError($relinfo)) {
728                 return $relinfo;
729             }
730
731             $ret[$package] = array(
732                 'version'  => $release['v'],
733                 'state'    => $release['s'],
734                 'filesize' => $relinfo['f'],
735             );
736         }
737
738         return $ret;
739     }
740
741     function packageInfo($base, $package, $channel = false)
742     {
743         PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
744         $pinfo = $this->_rest->retrieveData($base . 'p/' . strtolower($package) . '/info.xml', false, false, $channel);
745         if (PEAR::isError($pinfo)) {
746             PEAR::popErrorHandling();
747             return PEAR::raiseError('Unknown package: "' . $package . '" in channel "' . $channel . '"' . "\n". 'Debug: ' .
748                 $pinfo->getMessage());
749         }
750
751         $releases = array();
752         $allreleases = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
753             '/allreleases.xml', false, false, $channel);
754         if (!PEAR::isError($allreleases)) {
755             if (!class_exists('PEAR_PackageFile_v2')) {
756                 require_once 'PEAR/PackageFile/v2.php';
757             }
758
759             if (!is_array($allreleases['r']) || !isset($allreleases['r'][0])) {
760                 $allreleases['r'] = array($allreleases['r']);
761             }
762
763             $pf = new PEAR_PackageFile_v2;
764             foreach ($allreleases['r'] as $release) {
765                 $ds = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/deps.' .
766                     $release['v'] . '.txt', false, false, $channel);
767                 if (PEAR::isError($ds)) {
768                     continue;
769                 }
770
771                 if (!isset($latest)) {
772                     $latest = $release['v'];
773                 }
774
775                 $pf->setDeps(unserialize($ds));
776                 $ds = $pf->getDeps();
777                 $info = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package)
778                     . '/' . $release['v'] . '.xml', false, false, $channel);
779
780                 if (PEAR::isError($info)) {
781                     continue;
782                 }
783
784                 $releases[$release['v']] = array(
785                     'doneby' => $info['m'],
786                     'license' => $info['l'],
787                     'summary' => $info['s'],
788                     'description' => $info['d'],
789                     'releasedate' => $info['da'],
790                     'releasenotes' => $info['n'],
791                     'state' => $release['s'],
792                     'deps' => $ds ? $ds : array(),
793                 );
794             }
795         } else {
796             $latest = '';
797         }
798
799         PEAR::popErrorHandling();
800         if (isset($pinfo['dc']) && isset($pinfo['dp'])) {
801             if (is_array($pinfo['dp'])) {
802                 $deprecated = array('channel' => (string) $pinfo['dc'],
803                                     'package' => trim($pinfo['dp']['_content']));
804             } else {
805                 $deprecated = array('channel' => (string) $pinfo['dc'],
806                                     'package' => trim($pinfo['dp']));
807             }
808         } else {
809             $deprecated = false;
810         }
811
812         if (!isset($latest)) {
813             $latest = '';
814         }
815
816         return array(
817             'name' => $pinfo['n'],
818             'channel' => $pinfo['c'],
819             'category' => $pinfo['ca']['_content'],
820             'stable' => $latest,
821             'license' => $pinfo['l'],
822             'summary' => $pinfo['s'],
823             'description' => $pinfo['d'],
824             'releases' => $releases,
825             'deprecated' => $deprecated,
826             );
827     }
828
829     /**
830      * Return an array containing all of the states that are more stable than
831      * or equal to the passed in state
832      *
833      * @param string Release state
834      * @param boolean Determines whether to include $state in the list
835      * @return false|array False if $state is not a valid release state
836      */
837     function betterStates($state, $include = false)
838     {
839         static $states = array('snapshot', 'devel', 'alpha', 'beta', 'stable');
840         $i = array_search($state, $states);
841         if ($i === false) {
842             return false;
843         }
844
845         if ($include) {
846             $i--;
847         }
848
849         return array_slice($states, $i + 1);
850     }
851
852     /**
853      * Sort releases by version number
854      *
855      * @access private
856      */
857     function _sortReleasesByVersionNumber($a, $b)
858     {
859         if (version_compare($a['v'], $b['v'], '=')) {
860             return 0;
861         }
862
863         if (version_compare($a['v'], $b['v'], '>')) {
864             return -1;
865         }
866
867         if (version_compare($a['v'], $b['v'], '<')) {
868             return 1;
869         }
870     }
871 }