1<?php
2//
3// +----------------------------------------------------------------------+
4// | PHP Version 5                                                        |
5// +----------------------------------------------------------------------+
6// | Copyright (c) 1997-2004 The PHP Group                                |
7// +----------------------------------------------------------------------+
8// | This source file is subject to version 3.0 of the PHP license,       |
9// | that is bundled with this package in the file LICENSE, and is        |
10// | available through the world-wide-web at the following url:           |
11// | http://www.php.net/license/3_0.txt.                                  |
12// | If you did not receive a copy of the PHP license and are unable to   |
13// | obtain it through the world-wide-web, please send a note to          |
14// | license@php.net so we can mail you a copy immediately.               |
15// +----------------------------------------------------------------------+
16// | Authors: Tomas V.V.Cox <cox@idecnet.com>                             |
17// |          Stig Bakken <ssb@php.net>                                   |
18// +----------------------------------------------------------------------+
19//
20// THIS FILE IS DEPRECATED IN FAVOR OF DEPENDENCY2.PHP, AND IS NOT USED IN THE INSTALLER
21// $Id: Dependency.php,v 1.41 2006/01/06 04:47:36 cellog Exp $
22
23require_once "PEAR.php";
24require_once "OS/Guess.php";
25
26define('PEAR_DEPENDENCY_MISSING',        -1);
27define('PEAR_DEPENDENCY_CONFLICT',       -2);
28define('PEAR_DEPENDENCY_UPGRADE_MINOR',  -3);
29define('PEAR_DEPENDENCY_UPGRADE_MAJOR',  -4);
30define('PEAR_DEPENDENCY_BAD_DEPENDENCY', -5);
31define('PEAR_DEPENDENCY_MISSING_OPTIONAL', -6);
32define('PEAR_DEPENDENCY_CONFLICT_OPTIONAL',       -7);
33define('PEAR_DEPENDENCY_UPGRADE_MINOR_OPTIONAL',  -8);
34define('PEAR_DEPENDENCY_UPGRADE_MAJOR_OPTIONAL',  -9);
35
36/**
37 * Dependency check for PEAR packages
38 *
39 * The class is based on the dependency RFC that can be found at
40 * http://cvs.php.net/cvs.php/pearweb/rfc. It requires PHP >= 4.1
41 *
42 * @author Tomas V.V.Vox <cox@idecnet.com>
43 * @author Stig Bakken <ssb@php.net>
44 */
45class PEAR_Dependency
46{
47    // {{{ constructor
48    /**
49     * Constructor
50     *
51     * @access public
52     * @param  object Registry object
53     * @return void
54     */
55    function PEAR_Dependency(&$registry)
56    {
57        $this->registry = &$registry;
58    }
59
60    // }}}
61    // {{{ callCheckMethod()
62
63    /**
64    * This method maps the XML dependency definition to the
65    * corresponding one from PEAR_Dependency
66    *
67    * <pre>
68    * $opts => Array
69    *    (
70    *        [type] => pkg
71    *        [rel] => ge
72    *        [version] => 3.4
73    *        [name] => HTML_Common
74    *        [optional] => false
75    *    )
76    * </pre>
77    *
78    * @param  string Error message
79    * @param  array  Options
80    * @return boolean
81    */
82    function callCheckMethod(&$errmsg, $opts)
83    {
84        $rel = isset($opts['rel']) ? $opts['rel'] : 'has';
85        $req = isset($opts['version']) ? $opts['version'] : null;
86        $name = isset($opts['name']) ? $opts['name'] : null;
87        $channel = isset($opts['channel']) ? $opts['channel'] : 'pear.php.net';
88        $opt = (isset($opts['optional']) && $opts['optional'] == 'yes') ?
89            $opts['optional'] : null;
90        $errmsg = '';
91        switch ($opts['type']) {
92            case 'pkg':
93                return $this->checkPackage($errmsg, $name, $req, $rel, $opt, $channel);
94                break;
95            case 'ext':
96                return $this->checkExtension($errmsg, $name, $req, $rel, $opt);
97                break;
98            case 'php':
99                return $this->checkPHP($errmsg, $req, $rel);
100                break;
101            case 'prog':
102                return $this->checkProgram($errmsg, $name);
103                break;
104            case 'os':
105                return $this->checkOS($errmsg, $name);
106                break;
107            case 'sapi':
108                return $this->checkSAPI($errmsg, $name);
109                break;
110            case 'zend':
111                return $this->checkZend($errmsg, $name);
112                break;
113            default:
114                return "'{$opts['type']}' dependency type not supported";
115        }
116    }
117
118    // }}}
119    // {{{ checkPackage()
120
121    /**
122     * Package dependencies check method
123     *
124     * @param string $errmsg    Empty string, it will be populated with an error message, if any
125     * @param string $name      Name of the package to test
126     * @param string $req       The package version required
127     * @param string $relation  How to compare versions with each other
128     * @param bool   $opt       Whether the relationship is optional
129     * @param string $channel   Channel name
130     *
131     * @return mixed bool false if no error or the error string
132     */
133    function checkPackage(&$errmsg, $name, $req = null, $relation = 'has',
134                          $opt = false, $channel = 'pear.php.net')
135    {
136        if (is_string($req) && substr($req, 0, 2) == 'v.') {
137            $req = substr($req, 2);
138        }
139        switch ($relation) {
140            case 'has':
141                if (!$this->registry->packageExists($name, $channel)) {
142                    if ($opt) {
143                        $errmsg = "package `$channel/$name' is recommended to utilize some features.";
144                        return PEAR_DEPENDENCY_MISSING_OPTIONAL;
145                    }
146                    $errmsg = "requires package `$channel/$name'";
147                    return PEAR_DEPENDENCY_MISSING;
148                }
149                return false;
150            case 'not':
151                if ($this->registry->packageExists($name, $channel)) {
152                    $errmsg = "conflicts with package `$channel/$name'";
153                    return PEAR_DEPENDENCY_CONFLICT;
154                }
155                return false;
156            case 'lt':
157            case 'le':
158            case 'eq':
159            case 'ne':
160            case 'ge':
161            case 'gt':
162                $version = $this->registry->packageInfo($name, 'version', $channel);
163                if (!$this->registry->packageExists($name, $channel)
164                    || !version_compare("$version", "$req", $relation))
165                {
166                    $code = $this->codeFromRelation($relation, $version, $req, $opt);
167                    if ($opt) {
168                        $errmsg = "package `$channel/$name' version " . $this->signOperator($relation) .
169                            " $req is recommended to utilize some features.";
170                        if ($version) {
171                            $errmsg .= "  Installed version is $version";
172                        }
173                        return $code;
174                    }
175                    $errmsg = "requires package `$channel/$name' " .
176                        $this->signOperator($relation) . " $req";
177                    return $code;
178                }
179                return false;
180        }
181        $errmsg = "relation '$relation' with requirement '$req' is not supported (name=$channel/$name)";
182        return PEAR_DEPENDENCY_BAD_DEPENDENCY;
183    }
184
185    // }}}
186    // {{{ checkPackageUninstall()
187
188    /**
189     * Check package dependencies on uninstall
190     *
191     * @param string $error     The resultant error string
192     * @param string $warning   The resultant warning string
193     * @param string $name      Name of the package to test
194     * @param string $channel   Channel name of the package
195     *
196     * @return bool true if there were errors
197     */
198    function checkPackageUninstall(&$error, &$warning, $package, $channel = 'pear.php.net')
199    {
200        $channel = strtolower($channel);
201        $error = null;
202        $channels = $this->registry->listAllPackages();
203        foreach ($channels as $channelname => $packages) {
204            foreach ($packages as $pkg) {
205                if ($pkg == $package && $channel == $channelname) {
206                    continue;
207                }
208                $deps = $this->registry->packageInfo($pkg, 'release_deps', $channel);
209                if (empty($deps)) {
210                    continue;
211                }
212                foreach ($deps as $dep) {
213                    $depchannel = isset($dep['channel']) ? $dep['channel'] : 'pear.php.net';
214                    if ($dep['type'] == 'pkg' && (strcasecmp($dep['name'], $package) == 0) &&
215                          ($depchannel == $channel)) {
216                        if ($dep['rel'] == 'ne') {
217                            continue;
218                        }
219                        if (isset($dep['optional']) && $dep['optional'] == 'yes') {
220                            $warning .= "\nWarning: Package '$depchannel/$pkg' optionally depends on '$channel:/package'";
221                        } else {
222                            $error .= "Package '$depchannel/$pkg' depends on '$channel/$package'\n";
223                        }
224                    }
225                }
226            }
227        }
228        return ($error) ? true : false;
229    }
230
231    // }}}
232    // {{{ checkExtension()
233
234    /**
235     * Extension dependencies check method
236     *
237     * @param string $name        Name of the extension to test
238     * @param string $req_ext_ver Required extension version to compare with
239     * @param string $relation    How to compare versions with eachother
240     * @param bool   $opt         Whether the relationship is optional
241     *
242     * @return mixed bool false if no error or the error string
243     */
244    function checkExtension(&$errmsg, $name, $req = null, $relation = 'has',
245        $opt = false)
246    {
247        if ($relation == 'not') {
248            if (extension_loaded($name)) {
249                $errmsg = "conflicts with  PHP extension '$name'";
250                return PEAR_DEPENDENCY_CONFLICT;
251            } else {
252                return false;
253            }
254        }
255
256        if (!extension_loaded($name)) {
257            if ($relation == 'ne') {
258                return false;
259            }
260            if ($opt) {
261                $errmsg = "'$name' PHP extension is recommended to utilize some features";
262                return PEAR_DEPENDENCY_MISSING_OPTIONAL;
263            }
264            $errmsg = "'$name' PHP extension is not installed";
265            return PEAR_DEPENDENCY_MISSING;
266        }
267        if ($relation == 'has') {
268            return false;
269        }
270        $code = false;
271        if (is_string($req) && substr($req, 0, 2) == 'v.') {
272            $req = substr($req, 2);
273        }
274        $ext_ver = phpversion($name);
275        $operator = $relation;
276        // Force params to be strings, otherwise the comparation will fail (ex. 0.9==0.90)
277        if (!version_compare("$ext_ver", "$req", $operator)) {
278            $errmsg = "'$name' PHP extension version " .
279                $this->signOperator($operator) . " $req is required";
280            $code = $this->codeFromRelation($relation, $ext_ver, $req, $opt);
281            if ($opt) {
282                $errmsg = "'$name' PHP extension version " . $this->signOperator($operator) .
283                    " $req is recommended to utilize some features";
284                return $code;
285            }
286        }
287        return $code;
288    }
289
290    // }}}
291    // {{{ checkOS()
292
293    /**
294     * Operating system  dependencies check method
295     *
296     * @param string $os  Name of the operating system
297     *
298     * @return mixed bool false if no error or the error string
299     */
300    function checkOS(&$errmsg, $os)
301    {
302        // XXX Fixme: Implement a more flexible way, like
303        // comma separated values or something similar to PEAR_OS
304        static $myos;
305        if (empty($myos)) {
306            $myos = new OS_Guess();
307        }
308        // only 'has' relation is currently supported
309        if ($myos->matchSignature($os)) {
310            return false;
311        }
312        $errmsg = "'$os' operating system not supported";
313        return PEAR_DEPENDENCY_CONFLICT;
314    }
315
316    // }}}
317    // {{{ checkPHP()
318
319    /**
320     * PHP version check method
321     *
322     * @param string $req   which version to compare
323     * @param string $relation  how to compare the version
324     *
325     * @return mixed bool false if no error or the error string
326     */
327    function checkPHP(&$errmsg, $req, $relation = 'ge')
328    {
329        // this would be a bit stupid, but oh well :)
330        if ($relation == 'has') {
331            return false;
332        }
333        if ($relation == 'not') {
334            $errmsg = "Invalid dependency - 'not' is allowed when specifying PHP, you must run PHP in PHP";
335            return PEAR_DEPENDENCY_BAD_DEPENDENCY;
336        }
337        if (substr($req, 0, 2) == 'v.') {
338            $req = substr($req,2, strlen($req) - 2);
339        }
340        $php_ver = phpversion();
341        $operator = $relation;
342        if (!version_compare("$php_ver", "$req", $operator)) {
343            $errmsg = "PHP version " . $this->signOperator($operator) .
344                " $req is required";
345            return PEAR_DEPENDENCY_CONFLICT;
346        }
347        return false;
348    }
349
350    // }}}
351    // {{{ checkProgram()
352
353    /**
354     * External program check method.  Looks for executable files in
355     * directories listed in the PATH environment variable.
356     *
357     * @param string $program   which program to look for
358     *
359     * @return mixed bool false if no error or the error string
360     */
361    function checkProgram(&$errmsg, $program)
362    {
363        // XXX FIXME honor safe mode
364        $exe_suffix = OS_WINDOWS ? '.exe' : '';
365        $path_elements = explode(PATH_SEPARATOR, getenv('PATH'));
366        foreach ($path_elements as $dir) {
367            $file = $dir . DIRECTORY_SEPARATOR . $program . $exe_suffix;
368            if (@file_exists($file) && @is_executable($file)) {
369                return false;
370            }
371        }
372        $errmsg = "'$program' program is not present in the PATH";
373        return PEAR_DEPENDENCY_MISSING;
374    }
375
376    // }}}
377    // {{{ checkSAPI()
378
379    /**
380     * SAPI backend check method.  Version comparison is not yet
381     * available here.
382     *
383     * @param string $name      name of SAPI backend
384     * @param string $req   which version to compare
385     * @param string $relation  how to compare versions (currently
386     *                          hardcoded to 'has')
387     * @return mixed bool false if no error or the error string
388     */
389    function checkSAPI(&$errmsg, $name, $req = null, $relation = 'has')
390    {
391        // XXX Fixme: There is no way to know if the user has or
392        // not other SAPI backends installed than the installer one
393
394        $sapi_backend = php_sapi_name();
395        // Version comparisons not supported, sapi backends don't have
396        // version information yet.
397        if ($sapi_backend == $name) {
398            return false;
399        }
400        $errmsg = "'$sapi_backend' SAPI backend not supported";
401        return PEAR_DEPENDENCY_CONFLICT;
402    }
403
404    // }}}
405    // {{{ checkZend()
406
407    /**
408     * Zend version check method
409     *
410     * @param string $req   which version to compare
411     * @param string $relation  how to compare the version
412     *
413     * @return mixed bool false if no error or the error string
414     */
415    function checkZend(&$errmsg, $req, $relation = 'ge')
416    {
417        if (substr($req, 0, 2) == 'v.') {
418            $req = substr($req,2, strlen($req) - 2);
419        }
420        $zend_ver = zend_version();
421        $operator = substr($relation,0,2);
422        if (!version_compare("$zend_ver", "$req", $operator)) {
423            $errmsg = "Zend version " . $this->signOperator($operator) .
424                " $req is required";
425            return PEAR_DEPENDENCY_CONFLICT;
426        }
427        return false;
428    }
429
430    // }}}
431    // {{{ signOperator()
432
433    /**
434     * Converts text comparing operators to them sign equivalents
435     *
436     * Example: 'ge' to '>='
437     *
438     * @access public
439     * @param  string Operator
440     * @return string Sign equivalent
441     */
442    function signOperator($operator)
443    {
444        switch($operator) {
445            case 'lt': return '<';
446            case 'le': return '<=';
447            case 'gt': return '>';
448            case 'ge': return '>=';
449            case 'eq': return '==';
450            case 'ne': return '!=';
451            default:
452                return $operator;
453        }
454    }
455
456    // }}}
457    // {{{ codeFromRelation()
458
459    /**
460     * Convert relation into corresponding code
461     *
462     * @access public
463     * @param  string Relation
464     * @param  string Version
465     * @param  string Requirement
466     * @param  bool   Optional dependency indicator
467     * @return integer
468     */
469    function codeFromRelation($relation, $version, $req, $opt = false)
470    {
471        $code = PEAR_DEPENDENCY_BAD_DEPENDENCY;
472        switch ($relation) {
473            case 'gt': case 'ge': case 'eq':
474                // upgrade
475                $have_major = preg_replace('/\D.*/', '', $version);
476                $need_major = preg_replace('/\D.*/', '', $req);
477                if ($need_major > $have_major) {
478                    $code = $opt ? PEAR_DEPENDENCY_UPGRADE_MAJOR_OPTIONAL :
479                                   PEAR_DEPENDENCY_UPGRADE_MAJOR;
480                } else {
481                    $code = $opt ? PEAR_DEPENDENCY_UPGRADE_MINOR_OPTIONAL :
482                                   PEAR_DEPENDENCY_UPGRADE_MINOR;
483                }
484                break;
485            case 'lt': case 'le': case 'ne':
486                $code = $opt ? PEAR_DEPENDENCY_CONFLICT_OPTIONAL :
487                               PEAR_DEPENDENCY_CONFLICT;
488                break;
489        }
490        return $code;
491    }
492
493    // }}}
494}
495?>
496