1<?php
2/**
3 * PEAR_REST_13
4 *
5 * PHP versions 4 and 5
6 *
7 * @category   pear
8 * @package    PEAR
9 * @author     Greg Beaver <cellog@php.net>
10 * @copyright  1997-2009 The Authors
11 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
12 * @link       http://pear.php.net/package/PEAR
13 * @since      File available since Release 1.4.0a12
14 */
15
16/**
17 * For downloading REST xml/txt files
18 */
19require_once 'PEAR/REST.php';
20require_once 'PEAR/REST/10.php';
21
22/**
23 * Implement REST 1.3
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: @package_version@
31 * @link       http://pear.php.net/package/PEAR
32 * @since      Class available since Release 1.4.0a12
33 */
34class PEAR_REST_13 extends PEAR_REST_10
35{
36    /**
37     * Retrieve information about a remote package to be downloaded from a REST server
38     *
39     * This is smart enough to resolve the minimum PHP version dependency prior to download
40     * @param string $base The uri to prepend to all REST calls
41     * @param array $packageinfo an array of format:
42     * <pre>
43     *  array(
44     *   'package' => 'packagename',
45     *   'channel' => 'channelname',
46     *  ['state' => 'alpha' (or valid state),]
47     *  -or-
48     *  ['version' => '1.whatever']
49     * </pre>
50     * @param string $prefstate Current preferred_state config variable value
51     * @param bool $installed the installed version of this package to compare against
52     * @return array|false|PEAR_Error see {@link _returnDownloadURL()}
53     */
54    function getDownloadURL($base, $packageinfo, $prefstate, $installed, $channel = false)
55    {
56        $states = $this->betterStates($prefstate, true);
57        if (!$states) {
58            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
59        }
60
61        $channel  = $packageinfo['channel'];
62        $package  = $packageinfo['package'];
63        $state    = isset($packageinfo['state'])   ? $packageinfo['state']   : null;
64        $version  = isset($packageinfo['version']) ? $packageinfo['version'] : null;
65        $restFile = $base . 'r/' . strtolower($package) . '/allreleases2.xml';
66
67        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
68        if (PEAR::isError($info)) {
69            return PEAR::raiseError('No releases available for package "' .
70                $channel . '/' . $package . '"');
71        }
72
73        if (!isset($info['r'])) {
74            return false;
75        }
76
77        $release = $found = false;
78        if (!is_array($info['r']) || !isset($info['r'][0])) {
79            $info['r'] = array($info['r']);
80        }
81
82        $skippedphp = false;
83        foreach ($info['r'] as $release) {
84            if (!isset($this->_rest->_options['force']) && ($installed &&
85                  version_compare($release['v'], $installed, '<'))) {
86                continue;
87            }
88
89            if (isset($state)) {
90                // try our preferred state first
91                if ($release['s'] == $state) {
92                    if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
93                        // skip releases that require a PHP version newer than our PHP version
94                        $skippedphp = $release;
95                        continue;
96                    }
97                    $found = true;
98                    break;
99                }
100
101                // see if there is something newer and more stable
102                // bug #7221
103                if (in_array($release['s'], $this->betterStates($state), true)) {
104                    if (!isset($version) && version_compare($release['m'], phpversion(), '>')) {
105                        // skip releases that require a PHP version newer than our PHP version
106                        $skippedphp = $release;
107                        continue;
108                    }
109                    $found = true;
110                    break;
111                }
112            } elseif (isset($version)) {
113                if ($release['v'] == $version) {
114                    if (!isset($this->_rest->_options['force']) &&
115                          !isset($version) &&
116                          version_compare($release['m'], phpversion(), '>')) {
117                        // skip releases that require a PHP version newer than our PHP version
118                        $skippedphp = $release;
119                        continue;
120                    }
121                    $found = true;
122                    break;
123                }
124            } else {
125                if (in_array($release['s'], $states)) {
126                    if (version_compare($release['m'], phpversion(), '>')) {
127                        // skip releases that require a PHP version newer than our PHP version
128                        $skippedphp = $release;
129                        continue;
130                    }
131                    $found = true;
132                    break;
133                }
134            }
135        }
136
137        if (!$found && $skippedphp) {
138            $found = null;
139        }
140
141        return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
142    }
143
144    function getDepDownloadURL($base, $xsdversion, $dependency, $deppackage,
145                               $prefstate = 'stable', $installed = false, $channel = false)
146    {
147        $states = $this->betterStates($prefstate, true);
148        if (!$states) {
149            return PEAR::raiseError('"' . $prefstate . '" is not a valid state');
150        }
151
152        $channel  = $dependency['channel'];
153        $package  = $dependency['name'];
154        $state    = isset($dependency['state'])   ? $dependency['state']   : null;
155        $version  = isset($dependency['version']) ? $dependency['version'] : null;
156        $restFile = $base . 'r/' . strtolower($package) .'/allreleases2.xml';
157
158        $info = $this->_rest->retrieveData($restFile, false, false, $channel);
159        if (PEAR::isError($info)) {
160            return PEAR::raiseError('Package "' . $deppackage['channel'] . '/' . $deppackage['package']
161                . '" dependency "' . $channel . '/' . $package . '" has no releases');
162        }
163
164        if (!is_array($info) || !isset($info['r'])) {
165            return false;
166        }
167
168        $exclude = array();
169        $min = $max = $recommended = false;
170        if ($xsdversion == '1.0') {
171            $pinfo['package'] = $dependency['name'];
172            $pinfo['channel'] = 'pear.php.net'; // this is always true - don't change this
173            switch ($dependency['rel']) {
174                case 'ge' :
175                    $min = $dependency['version'];
176                break;
177                case 'gt' :
178                    $min = $dependency['version'];
179                    $exclude = array($dependency['version']);
180                break;
181                case 'eq' :
182                    $recommended = $dependency['version'];
183                break;
184                case 'lt' :
185                    $max = $dependency['version'];
186                    $exclude = array($dependency['version']);
187                break;
188                case 'le' :
189                    $max = $dependency['version'];
190                break;
191                case 'ne' :
192                    $exclude = array($dependency['version']);
193                break;
194            }
195        } else {
196            $pinfo['package'] = $dependency['name'];
197            $min = isset($dependency['min']) ? $dependency['min'] : false;
198            $max = isset($dependency['max']) ? $dependency['max'] : false;
199            $recommended = isset($dependency['recommended']) ?
200                $dependency['recommended'] : false;
201            if (isset($dependency['exclude'])) {
202                if (!isset($dependency['exclude'][0])) {
203                    $exclude = array($dependency['exclude']);
204                }
205            }
206        }
207
208        $skippedphp = $found = $release = false;
209        if (!is_array($info['r']) || !isset($info['r'][0])) {
210            $info['r'] = array($info['r']);
211        }
212
213        foreach ($info['r'] as $release) {
214            if (!isset($this->_rest->_options['force']) && ($installed &&
215                  version_compare($release['v'], $installed, '<'))) {
216                continue;
217            }
218
219            if (in_array($release['v'], $exclude)) { // skip excluded versions
220                continue;
221            }
222
223            // allow newer releases to say "I'm OK with the dependent package"
224            if ($xsdversion == '2.0' && isset($release['co'])) {
225                if (!is_array($release['co']) || !isset($release['co'][0])) {
226                    $release['co'] = array($release['co']);
227                }
228
229                foreach ($release['co'] as $entry) {
230                    if (isset($entry['x']) && !is_array($entry['x'])) {
231                        $entry['x'] = array($entry['x']);
232                    } elseif (!isset($entry['x'])) {
233                        $entry['x'] = array();
234                    }
235
236                    if ($entry['c'] == $deppackage['channel'] &&
237                          strtolower($entry['p']) == strtolower($deppackage['package']) &&
238                          version_compare($deppackage['version'], $entry['min'], '>=') &&
239                          version_compare($deppackage['version'], $entry['max'], '<=') &&
240                          !in_array($release['v'], $entry['x'])) {
241                        if (version_compare($release['m'], phpversion(), '>')) {
242                            // skip dependency releases that require a PHP version
243                            // newer than our PHP version
244                            $skippedphp = $release;
245                            continue;
246                        }
247
248                        $recommended = $release['v'];
249                        break;
250                    }
251                }
252            }
253
254            if ($recommended) {
255                if ($release['v'] != $recommended) { // if we want a specific
256                    // version, then skip all others
257                    continue;
258                }
259
260                if (!in_array($release['s'], $states)) {
261                    // the stability is too low, but we must return the
262                    // recommended version if possible
263                    return $this->_returnDownloadURL($base, $package, $release, $info, true, false, $channel);
264                }
265            }
266
267            if ($min && version_compare($release['v'], $min, 'lt')) { // skip too old versions
268                continue;
269            }
270
271            if ($max && version_compare($release['v'], $max, 'gt')) { // skip too new versions
272                continue;
273            }
274
275            if ($installed && version_compare($release['v'], $installed, '<')) {
276                continue;
277            }
278
279            if (in_array($release['s'], $states)) { // if in the preferred state...
280                if (version_compare($release['m'], phpversion(), '>')) {
281                    // skip dependency releases that require a PHP version
282                    // newer than our PHP version
283                    $skippedphp = $release;
284                    continue;
285                }
286
287                $found = true; // ... then use it
288                break;
289            }
290        }
291
292        if (!$found && $skippedphp) {
293            $found = null;
294        }
295
296        return $this->_returnDownloadURL($base, $package, $release, $info, $found, $skippedphp, $channel);
297    }
298
299    /**
300     * List package upgrades but take the PHP version into account.
301     */
302    function listLatestUpgrades($base, $pref_state, $installed, $channel, &$reg)
303    {
304        $packagelist = $this->_rest->retrieveData($base . 'p/packages.xml', false, false, $channel);
305        if (PEAR::isError($packagelist)) {
306            return $packagelist;
307        }
308
309        $ret = array();
310        if (!is_array($packagelist) || !isset($packagelist['p'])) {
311            return $ret;
312        }
313
314        if (!is_array($packagelist['p'])) {
315            $packagelist['p'] = array($packagelist['p']);
316        }
317
318        foreach ($packagelist['p'] as $package) {
319            if (!isset($installed[strtolower($package)])) {
320                continue;
321            }
322
323            $inst_version = $reg->packageInfo($package, 'version', $channel);
324            $inst_state   = $reg->packageInfo($package, 'release_state', $channel);
325            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
326            $info = $this->_rest->retrieveData($base . 'r/' . strtolower($package) .
327                '/allreleases2.xml', false, false, $channel);
328            PEAR::popErrorHandling();
329            if (PEAR::isError($info)) {
330                continue; // no remote releases
331            }
332
333            if (!isset($info['r'])) {
334                continue;
335            }
336
337            $release = $found = false;
338            if (!is_array($info['r']) || !isset($info['r'][0])) {
339                $info['r'] = array($info['r']);
340            }
341
342            // $info['r'] is sorted by version number
343            usort($info['r'], array($this, '_sortReleasesByVersionNumber'));
344            foreach ($info['r'] as $release) {
345                if ($inst_version && version_compare($release['v'], $inst_version, '<=')) {
346                    // not newer than the one installed
347                    break;
348                }
349                if (version_compare($release['m'], phpversion(), '>')) {
350                    // skip dependency releases that require a PHP version
351                    // newer than our PHP version
352                    continue;
353                }
354
355                // new version > installed version
356                if (!$pref_state) {
357                    // every state is a good state
358                    $found = true;
359                    break;
360                } else {
361                    $new_state = $release['s'];
362                    // if new state >= installed state: go
363                    if (in_array($new_state, $this->betterStates($inst_state, true))) {
364                        $found = true;
365                        break;
366                    } else {
367                        // only allow to lower the state of package,
368                        // if new state >= preferred state: go
369                        if (in_array($new_state, $this->betterStates($pref_state, true))) {
370                            $found = true;
371                            break;
372                        }
373                    }
374                }
375            }
376
377            if (!$found) {
378                continue;
379            }
380
381            $relinfo = $this->_rest->retrieveCacheFirst($base . 'r/' . strtolower($package) . '/' .
382                $release['v'] . '.xml', false, false, $channel);
383            if (PEAR::isError($relinfo)) {
384                return $relinfo;
385            }
386
387            $ret[$package] = array(
388                'version'  => $release['v'],
389                'state'    => $release['s'],
390                'filesize' => $relinfo['f'],
391            );
392        }
393
394        return $ret;
395    }
396}