1<?php
2/**
3 * PEAR_Dependency2, advanced dependency validation
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.0a1
14 */
15
16/**
17 * Required for the PEAR_VALIDATE_* constants
18 */
19require_once 'PEAR/Validate.php';
20
21/**
22 * Dependency check for PEAR packages
23 *
24 * This class handles both version 1.0 and 2.0 dependencies
25 * WARNING: *any* changes to this class must be duplicated in the
26 * test_PEAR_Dependency2 class found in tests/PEAR_Dependency2/setup.php.inc,
27 * or unit tests will not actually validate the changes
28 * @category   pear
29 * @package    PEAR
30 * @author     Greg Beaver <cellog@php.net>
31 * @copyright  1997-2009 The Authors
32 * @license    http://opensource.org/licenses/bsd-license.php New BSD License
33 * @version    Release: @PEAR-VER@
34 * @link       http://pear.php.net/package/PEAR
35 * @since      Class available since Release 1.4.0a1
36 */
37class PEAR_Dependency2
38{
39    /**
40     * One of the PEAR_VALIDATE_* states
41     * @see PEAR_VALIDATE_NORMAL
42     * @var integer
43     */
44    var $_state;
45
46    /**
47     * Command-line options to install/upgrade/uninstall commands
48     * @param array
49     */
50    var $_options;
51
52    /**
53     * @var OS_Guess
54     */
55    var $_os;
56
57    /**
58     * @var PEAR_Registry
59     */
60    var $_registry;
61
62    /**
63     * @var PEAR_Config
64     */
65    var $_config;
66
67    /**
68     * @var PEAR_DependencyDB
69     */
70    var $_dependencydb;
71
72    /**
73     * Output of PEAR_Registry::parsedPackageName()
74     * @var array
75     */
76    var $_currentPackage;
77
78    /**
79     * @param PEAR_Config
80     * @param array installation options
81     * @param array format of PEAR_Registry::parsedPackageName()
82     * @param int installation state (one of PEAR_VALIDATE_*)
83     */
84    function __construct(&$config, $installoptions, $package,
85                              $state = PEAR_VALIDATE_INSTALLING)
86    {
87        $this->_config = &$config;
88        if (!class_exists('PEAR_DependencyDB')) {
89            require_once 'PEAR/DependencyDB.php';
90        }
91
92        if (isset($installoptions['packagingroot'])) {
93            // make sure depdb is in the right location
94            $config->setInstallRoot($installoptions['packagingroot']);
95        }
96
97        $this->_registry = &$config->getRegistry();
98        $this->_dependencydb = &PEAR_DependencyDB::singleton($config);
99        if (isset($installoptions['packagingroot'])) {
100            $config->setInstallRoot(false);
101        }
102
103        $this->_options = $installoptions;
104        $this->_state = $state;
105        if (!class_exists('OS_Guess')) {
106            require_once 'OS/Guess.php';
107        }
108
109        $this->_os = new OS_Guess;
110        $this->_currentPackage = $package;
111    }
112
113    static function _getExtraString($dep)
114    {
115        $extra = ' (';
116        if (isset($dep['uri'])) {
117            return '';
118        }
119
120        if (isset($dep['recommended'])) {
121            $extra .= 'recommended version ' . $dep['recommended'];
122        } else {
123            if (isset($dep['min'])) {
124                $extra .= 'version >= ' . $dep['min'];
125            }
126
127            if (isset($dep['max'])) {
128                if ($extra != ' (') {
129                    $extra .= ', ';
130                }
131                $extra .= 'version <= ' . $dep['max'];
132            }
133
134            if (isset($dep['exclude'])) {
135                if (!is_array($dep['exclude'])) {
136                    $dep['exclude'] = array($dep['exclude']);
137                }
138
139                if ($extra != ' (') {
140                    $extra .= ', ';
141                }
142
143                $extra .= 'excluded versions: ';
144                foreach ($dep['exclude'] as $i => $exclude) {
145                    if ($i) {
146                        $extra .= ', ';
147                    }
148                    $extra .= $exclude;
149                }
150            }
151        }
152
153        $extra .= ')';
154        if ($extra == ' ()') {
155            $extra = '';
156        }
157
158        return $extra;
159    }
160
161    /**
162     * This makes unit-testing a heck of a lot easier
163     */
164    function getPHP_OS()
165    {
166        return PHP_OS;
167    }
168
169    /**
170     * This makes unit-testing a heck of a lot easier
171     */
172    function getsysname()
173    {
174        return $this->_os->getSysname();
175    }
176
177    /**
178     * Specify a dependency on an OS.  Use arch for detailed os/processor information
179     *
180     * There are two generic OS dependencies that will be the most common, unix and windows.
181     * Other options are linux, freebsd, darwin (OS X), sunos, irix, hpux, aix
182     */
183    function validateOsDependency($dep)
184    {
185        if ($this->_state != PEAR_VALIDATE_INSTALLING && $this->_state != PEAR_VALIDATE_DOWNLOADING) {
186            return true;
187        }
188
189        if ($dep['name'] == '*') {
190            return true;
191        }
192
193        $not = isset($dep['conflicts']) ? true : false;
194        switch (strtolower($dep['name'])) {
195            case 'windows' :
196                if ($not) {
197                    if (strtolower(substr($this->getPHP_OS(), 0, 3)) == 'win') {
198                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
199                            return $this->raiseError("Cannot install %s on Windows");
200                        }
201
202                        return $this->warning("warning: Cannot install %s on Windows");
203                    }
204                } else {
205                    if (strtolower(substr($this->getPHP_OS(), 0, 3)) != 'win') {
206                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
207                            return $this->raiseError("Can only install %s on Windows");
208                        }
209
210                        return $this->warning("warning: Can only install %s on Windows");
211                    }
212                }
213            break;
214            case 'unix' :
215                $unices = array('linux', 'freebsd', 'darwin', 'sunos', 'irix', 'hpux', 'aix');
216                if ($not) {
217                    if (in_array($this->getSysname(), $unices)) {
218                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
219                            return $this->raiseError("Cannot install %s on any Unix system");
220                        }
221
222                        return $this->warning( "warning: Cannot install %s on any Unix system");
223                    }
224                } else {
225                    if (!in_array($this->getSysname(), $unices)) {
226                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
227                            return $this->raiseError("Can only install %s on a Unix system");
228                        }
229
230                        return $this->warning("warning: Can only install %s on a Unix system");
231                    }
232                }
233            break;
234            default :
235                if ($not) {
236                    if (strtolower($dep['name']) == strtolower($this->getSysname())) {
237                        if (!isset($this->_options['nodeps']) &&
238                              !isset($this->_options['force'])) {
239                            return $this->raiseError('Cannot install %s on ' . $dep['name'] .
240                                ' operating system');
241                        }
242
243                        return $this->warning('warning: Cannot install %s on ' .
244                            $dep['name'] . ' operating system');
245                    }
246                } else {
247                    if (strtolower($dep['name']) != strtolower($this->getSysname())) {
248                        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
249                            return $this->raiseError('Cannot install %s on ' .
250                                $this->getSysname() .
251                                ' operating system, can only install on ' . $dep['name']);
252                        }
253
254                        return $this->warning('warning: Cannot install %s on ' .
255                            $this->getSysname() .
256                            ' operating system, can only install on ' . $dep['name']);
257                    }
258                }
259        }
260        return true;
261    }
262
263    /**
264     * This makes unit-testing a heck of a lot easier
265     */
266    function matchSignature($pattern)
267    {
268        return $this->_os->matchSignature($pattern);
269    }
270
271    /**
272     * Specify a complex dependency on an OS/processor/kernel version,
273     * Use OS for simple operating system dependency.
274     *
275     * This is the only dependency that accepts an eregable pattern.  The pattern
276     * will be matched against the php_uname() output parsed by OS_Guess
277     */
278    function validateArchDependency($dep)
279    {
280        if ($this->_state != PEAR_VALIDATE_INSTALLING) {
281            return true;
282        }
283
284        $not = isset($dep['conflicts']) ? true : false;
285        if (!$this->matchSignature($dep['pattern'])) {
286            if (!$not) {
287                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
288                    return $this->raiseError('%s Architecture dependency failed, does not ' .
289                        'match "' . $dep['pattern'] . '"');
290                }
291
292                return $this->warning('warning: %s Architecture dependency failed, does ' .
293                    'not match "' . $dep['pattern'] . '"');
294            }
295
296            return true;
297        }
298
299        if ($not) {
300            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
301                return $this->raiseError('%s Architecture dependency failed, required "' .
302                    $dep['pattern'] . '"');
303            }
304
305            return $this->warning('warning: %s Architecture dependency failed, ' .
306                'required "' . $dep['pattern'] . '"');
307        }
308
309        return true;
310    }
311
312    /**
313     * This makes unit-testing a heck of a lot easier
314     */
315    function extension_loaded($name)
316    {
317        return extension_loaded($name);
318    }
319
320    /**
321     * This makes unit-testing a heck of a lot easier
322     */
323    function phpversion($name = null)
324    {
325        if ($name !== null) {
326            return phpversion($name);
327        }
328
329        return phpversion();
330    }
331
332    function validateExtensionDependency($dep, $required = true)
333    {
334        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
335              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
336            return true;
337        }
338
339        $loaded = $this->extension_loaded($dep['name']);
340        $extra  = self::_getExtraString($dep);
341        if (isset($dep['exclude'])) {
342            if (!is_array($dep['exclude'])) {
343                $dep['exclude'] = array($dep['exclude']);
344            }
345        }
346
347        if (!isset($dep['min']) && !isset($dep['max']) &&
348            !isset($dep['recommended']) && !isset($dep['exclude'])
349        ) {
350            if ($loaded) {
351                if (isset($dep['conflicts'])) {
352                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
353                        return $this->raiseError('%s conflicts with PHP extension "' .
354                            $dep['name'] . '"' . $extra);
355                    }
356
357                    return $this->warning('warning: %s conflicts with PHP extension "' .
358                        $dep['name'] . '"' . $extra);
359                }
360
361                return true;
362            }
363
364            if (isset($dep['conflicts'])) {
365                return true;
366            }
367
368            if ($required) {
369                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
370                    return $this->raiseError('%s requires PHP extension "' .
371                        $dep['name'] . '"' . $extra);
372                }
373
374                return $this->warning('warning: %s requires PHP extension "' .
375                    $dep['name'] . '"' . $extra);
376            }
377
378            return $this->warning('%s can optionally use PHP extension "' .
379                $dep['name'] . '"' . $extra);
380        }
381
382        if (!$loaded) {
383            if (isset($dep['conflicts'])) {
384                return true;
385            }
386
387            if (!$required) {
388                return $this->warning('%s can optionally use PHP extension "' .
389                    $dep['name'] . '"' . $extra);
390            }
391
392            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
393                return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
394                    '"' . $extra);
395            }
396
397            return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
398                    '"' . $extra);
399        }
400
401        $version = (string) $this->phpversion($dep['name']);
402        if (empty($version)) {
403            $version = '0';
404        }
405
406        $fail = false;
407        if (isset($dep['min']) && !version_compare($version, $dep['min'], '>=')) {
408            $fail = true;
409        }
410
411        if (isset($dep['max']) && !version_compare($version, $dep['max'], '<=')) {
412            $fail = true;
413        }
414
415        if ($fail && !isset($dep['conflicts'])) {
416            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
417                return $this->raiseError('%s requires PHP extension "' . $dep['name'] .
418                    '"' . $extra . ', installed version is ' . $version);
419            }
420
421            return $this->warning('warning: %s requires PHP extension "' . $dep['name'] .
422                '"' . $extra . ', installed version is ' . $version);
423        } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail && isset($dep['conflicts'])) {
424            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
425                return $this->raiseError('%s conflicts with PHP extension "' .
426                    $dep['name'] . '"' . $extra . ', installed version is ' . $version);
427            }
428
429            return $this->warning('warning: %s conflicts with PHP extension "' .
430                $dep['name'] . '"' . $extra . ', installed version is ' . $version);
431        }
432
433        if (isset($dep['exclude'])) {
434            foreach ($dep['exclude'] as $exclude) {
435                if (version_compare($version, $exclude, '==')) {
436                    if (isset($dep['conflicts'])) {
437                        continue;
438                    }
439
440                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
441                        return $this->raiseError('%s is not compatible with PHP extension "' .
442                            $dep['name'] . '" version ' .
443                            $exclude);
444                    }
445
446                    return $this->warning('warning: %s is not compatible with PHP extension "' .
447                        $dep['name'] . '" version ' .
448                        $exclude);
449                } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
450                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
451                        return $this->raiseError('%s conflicts with PHP extension "' .
452                            $dep['name'] . '"' . $extra . ', installed version is ' . $version);
453                    }
454
455                    return $this->warning('warning: %s conflicts with PHP extension "' .
456                        $dep['name'] . '"' . $extra . ', installed version is ' . $version);
457                }
458            }
459        }
460
461        if (isset($dep['recommended'])) {
462            if (version_compare($version, $dep['recommended'], '==')) {
463                return true;
464            }
465
466            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
467                return $this->raiseError('%s dependency: PHP extension ' . $dep['name'] .
468                    ' version "' . $version . '"' .
469                    ' is not the recommended version "' . $dep['recommended'] .
470                    '", but may be compatible, use --force to install');
471            }
472
473            return $this->warning('warning: %s dependency: PHP extension ' .
474                $dep['name'] . ' version "' . $version . '"' .
475                ' is not the recommended version "' . $dep['recommended'].'"');
476        }
477
478        return true;
479    }
480
481    function validatePhpDependency($dep)
482    {
483        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
484              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
485            return true;
486        }
487
488        $version = $this->phpversion();
489        $extra   = self::_getExtraString($dep);
490        if (isset($dep['exclude'])) {
491            if (!is_array($dep['exclude'])) {
492                $dep['exclude'] = array($dep['exclude']);
493            }
494        }
495
496        if (isset($dep['min'])) {
497            if (!version_compare($version, $dep['min'], '>=')) {
498                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
499                    return $this->raiseError('%s requires PHP' .
500                        $extra . ', installed version is ' . $version);
501                }
502
503                return $this->warning('warning: %s requires PHP' .
504                    $extra . ', installed version is ' . $version);
505            }
506        }
507
508        if (isset($dep['max'])) {
509            if (!version_compare($version, $dep['max'], '<=')) {
510                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
511                    return $this->raiseError('%s requires PHP' .
512                        $extra . ', installed version is ' . $version);
513                }
514
515                return $this->warning('warning: %s requires PHP' .
516                    $extra . ', installed version is ' . $version);
517            }
518        }
519
520        if (isset($dep['exclude'])) {
521            foreach ($dep['exclude'] as $exclude) {
522                if (version_compare($version, $exclude, '==')) {
523                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
524                        return $this->raiseError('%s is not compatible with PHP version ' .
525                            $exclude);
526                    }
527
528                    return $this->warning(
529                        'warning: %s is not compatible with PHP version ' .
530                        $exclude);
531                }
532            }
533        }
534
535        return true;
536    }
537
538    /**
539     * This makes unit-testing a heck of a lot easier
540     */
541    function getPEARVersion()
542    {
543        return '@PEAR-VER@';
544    }
545
546    function validatePearinstallerDependency($dep)
547    {
548        $pearversion = $this->getPEARVersion();
549        $extra = self::_getExtraString($dep);
550        if (isset($dep['exclude'])) {
551            if (!is_array($dep['exclude'])) {
552                $dep['exclude'] = array($dep['exclude']);
553            }
554        }
555
556        if (version_compare($pearversion, $dep['min'], '<')) {
557            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
558                return $this->raiseError('%s requires PEAR Installer' . $extra .
559                    ', installed version is ' . $pearversion);
560            }
561
562            return $this->warning('warning: %s requires PEAR Installer' . $extra .
563                ', installed version is ' . $pearversion);
564        }
565
566        if (isset($dep['max'])) {
567            if (version_compare($pearversion, $dep['max'], '>')) {
568                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
569                    return $this->raiseError('%s requires PEAR Installer' . $extra .
570                        ', installed version is ' . $pearversion);
571                }
572
573                return $this->warning('warning: %s requires PEAR Installer' . $extra .
574                    ', installed version is ' . $pearversion);
575            }
576        }
577
578        if (isset($dep['exclude'])) {
579            if (!isset($dep['exclude'][0])) {
580                $dep['exclude'] = array($dep['exclude']);
581            }
582
583            foreach ($dep['exclude'] as $exclude) {
584                if (version_compare($exclude, $pearversion, '==')) {
585                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
586                        return $this->raiseError('%s is not compatible with PEAR Installer ' .
587                            'version ' . $exclude);
588                    }
589
590                    return $this->warning('warning: %s is not compatible with PEAR ' .
591                        'Installer version ' . $exclude);
592                }
593            }
594        }
595
596        return true;
597    }
598
599    function validateSubpackageDependency($dep, $required, $params)
600    {
601        return $this->validatePackageDependency($dep, $required, $params);
602    }
603
604    /**
605     * @param array dependency information (2.0 format)
606     * @param boolean whether this is a required dependency
607     * @param array a list of downloaded packages to be installed, if any
608     * @param boolean if true, then deps on pear.php.net that fail will also check
609     *                against pecl.php.net packages to accommodate extensions that have
610     *                moved to pecl.php.net from pear.php.net
611     */
612    function validatePackageDependency($dep, $required, $params, $depv1 = false)
613    {
614        if ($this->_state != PEAR_VALIDATE_INSTALLING &&
615              $this->_state != PEAR_VALIDATE_DOWNLOADING) {
616            return true;
617        }
618
619        if (isset($dep['providesextension'])) {
620            if ($this->extension_loaded($dep['providesextension'])) {
621                $save = $dep;
622                $subdep = $dep;
623                $subdep['name'] = $subdep['providesextension'];
624                PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
625                $ret = $this->validateExtensionDependency($subdep, $required);
626                PEAR::popErrorHandling();
627                if (!PEAR::isError($ret)) {
628                    return true;
629                }
630            }
631        }
632
633        if ($this->_state == PEAR_VALIDATE_INSTALLING) {
634            return $this->_validatePackageInstall($dep, $required, $depv1);
635        }
636
637        if ($this->_state == PEAR_VALIDATE_DOWNLOADING) {
638            return $this->_validatePackageDownload($dep, $required, $params, $depv1);
639        }
640    }
641
642    function _validatePackageDownload($dep, $required, $params, $depv1 = false)
643    {
644        $dep['package'] = $dep['name'];
645        if (isset($dep['uri'])) {
646            $dep['channel'] = '__uri';
647        }
648
649        $depname = $this->_registry->parsedPackageNameToString($dep, true);
650        $found = false;
651        foreach ($params as $param) {
652            if ($param->isEqual(
653                  array('package' => $dep['name'],
654                        'channel' => $dep['channel']))) {
655                $found = true;
656                break;
657            }
658
659            if ($depv1 && $dep['channel'] == 'pear.php.net') {
660                if ($param->isEqual(
661                  array('package' => $dep['name'],
662                        'channel' => 'pecl.php.net'))) {
663                    $found = true;
664                    break;
665                }
666            }
667        }
668
669        if (!$found && isset($dep['providesextension'])) {
670            foreach ($params as $param) {
671                if ($param->isExtension($dep['providesextension'])) {
672                    $found = true;
673                    break;
674                }
675            }
676        }
677
678        if ($found) {
679            $version = $param->getVersion();
680            $installed = false;
681            $downloaded = true;
682        } else {
683            if ($this->_registry->packageExists($dep['name'], $dep['channel'])) {
684                $installed = true;
685                $downloaded = false;
686                $version = $this->_registry->packageinfo($dep['name'], 'version',
687                    $dep['channel']);
688            } else {
689                if ($dep['channel'] == 'pecl.php.net' && $this->_registry->packageExists($dep['name'],
690                      'pear.php.net')) {
691                    $installed = true;
692                    $downloaded = false;
693                    $version = $this->_registry->packageinfo($dep['name'], 'version',
694                        'pear.php.net');
695                } else {
696                    $version = 'not installed or downloaded';
697                    $installed = false;
698                    $downloaded = false;
699                }
700            }
701        }
702
703        $extra = self::_getExtraString($dep);
704        if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
705            $dep['exclude'] = array($dep['exclude']);
706        }
707
708        if (!isset($dep['min']) && !isset($dep['max']) &&
709              !isset($dep['recommended']) && !isset($dep['exclude'])
710        ) {
711            if ($installed || $downloaded) {
712                $installed = $installed ? 'installed' : 'downloaded';
713                if (isset($dep['conflicts'])) {
714                    $rest = '';
715                    if ($version) {
716                        $rest = ", $installed version is " . $version;
717                    }
718
719                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
720                        return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra . $rest);
721                    }
722
723                    return $this->warning('warning: %s conflicts with package "' . $depname . '"' . $extra . $rest);
724                }
725
726                return true;
727            }
728
729            if (isset($dep['conflicts'])) {
730                return true;
731            }
732
733            if ($required) {
734                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
735                    return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
736                }
737
738                return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
739            }
740
741            return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
742        }
743
744        if (!$installed && !$downloaded) {
745            if (isset($dep['conflicts'])) {
746                return true;
747            }
748
749            if ($required) {
750                if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
751                    return $this->raiseError('%s requires package "' . $depname . '"' . $extra);
752                }
753
754                return $this->warning('warning: %s requires package "' . $depname . '"' . $extra);
755            }
756
757            return $this->warning('%s can optionally use package "' . $depname . '"' . $extra);
758        }
759
760        $fail = false;
761        if (isset($dep['min']) && version_compare($version, $dep['min'], '<')) {
762            $fail = true;
763        }
764
765        if (isset($dep['max']) && version_compare($version, $dep['max'], '>')) {
766            $fail = true;
767        }
768
769        if ($fail && !isset($dep['conflicts'])) {
770            $installed = $installed ? 'installed' : 'downloaded';
771            $dep['package'] = $dep['name'];
772            $dep = $this->_registry->parsedPackageNameToString($dep, true);
773            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
774                return $this->raiseError('%s requires package "' . $depname . '"' .
775                    $extra . ", $installed version is " . $version);
776            }
777
778            return $this->warning('warning: %s requires package "' . $depname . '"' .
779                $extra . ", $installed version is " . $version);
780        } elseif ((isset($dep['min']) || isset($dep['max'])) && !$fail &&
781              isset($dep['conflicts']) && !isset($dep['exclude'])) {
782            $installed = $installed ? 'installed' : 'downloaded';
783            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
784                return $this->raiseError('%s conflicts with package "' . $depname . '"' . $extra .
785                    ", $installed version is " . $version);
786            }
787
788            return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
789                $extra . ", $installed version is " . $version);
790        }
791
792        if (isset($dep['exclude'])) {
793            $installed = $installed ? 'installed' : 'downloaded';
794            foreach ($dep['exclude'] as $exclude) {
795                if (version_compare($version, $exclude, '==') && !isset($dep['conflicts'])) {
796                    if (!isset($this->_options['nodeps']) &&
797                          !isset($this->_options['force'])
798                    ) {
799                        return $this->raiseError('%s is not compatible with ' .
800                            $installed . ' package "' .
801                            $depname . '" version ' .
802                            $exclude);
803                    }
804
805                    return $this->warning('warning: %s is not compatible with ' .
806                        $installed . ' package "' .
807                        $depname . '" version ' .
808                        $exclude);
809                } elseif (version_compare($version, $exclude, '!=') && isset($dep['conflicts'])) {
810                    $installed = $installed ? 'installed' : 'downloaded';
811                    if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
812                        return $this->raiseError('%s conflicts with package "' . $depname . '"' .
813                            $extra . ", $installed version is " . $version);
814                    }
815
816                    return $this->warning('warning: %s conflicts with package "' . $depname . '"' .
817                        $extra . ", $installed version is " . $version);
818                }
819            }
820        }
821
822        if (isset($dep['recommended'])) {
823            $installed = $installed ? 'installed' : 'downloaded';
824            if (version_compare($version, $dep['recommended'], '==')) {
825                return true;
826            }
827
828            if (!$found && $installed) {
829                $param = $this->_registry->getPackage($dep['name'], $dep['channel']);
830            }
831
832            if ($param) {
833                $found = false;
834                foreach ($params as $parent) {
835                    if ($parent->isEqual($this->_currentPackage)) {
836                        $found = true;
837                        break;
838                    }
839                }
840
841                if ($found) {
842                    if ($param->isCompatible($parent)) {
843                        return true;
844                    }
845                } else { // this is for validPackage() calls
846                    $parent = $this->_registry->getPackage($this->_currentPackage['package'],
847                        $this->_currentPackage['channel']);
848                    if ($parent !== null && $param->isCompatible($parent)) {
849                        return true;
850                    }
851                }
852            }
853
854            if (!isset($this->_options['nodeps']) && !isset($this->_options['force']) &&
855                  !isset($this->_options['loose'])
856            ) {
857                return $this->raiseError('%s dependency package "' . $depname .
858                    '" ' . $installed . ' version ' . $version .
859                    ' is not the recommended version ' . $dep['recommended'] .
860                    ', but may be compatible, use --force to install');
861            }
862
863            return $this->warning('warning: %s dependency package "' . $depname .
864                '" ' . $installed . ' version ' . $version .
865                ' is not the recommended version ' . $dep['recommended']);
866        }
867
868        return true;
869    }
870
871    function _validatePackageInstall($dep, $required, $depv1 = false)
872    {
873        return $this->_validatePackageDownload($dep, $required, array(), $depv1);
874    }
875
876    /**
877     * Verify that uninstalling packages passed in to command line is OK.
878     *
879     * @param PEAR_Installer $dl
880     * @return PEAR_Error|true
881     */
882    function validatePackageUninstall(&$dl)
883    {
884        if (PEAR::isError($this->_dependencydb)) {
885            return $this->_dependencydb;
886        }
887
888        $params = array();
889        // construct an array of "downloaded" packages to fool the package dependency checker
890        // into using these to validate uninstalls of circular dependencies
891        $downloaded = &$dl->getUninstallPackages();
892        foreach ($downloaded as $i => $pf) {
893            if (!class_exists('PEAR_Downloader_Package')) {
894                require_once 'PEAR/Downloader/Package.php';
895            }
896            $dp = new PEAR_Downloader_Package($dl);
897            $dp->setPackageFile($downloaded[$i]);
898            $params[$i] = $dp;
899        }
900
901        // check cache
902        $memyselfandI = strtolower($this->_currentPackage['channel']) . '/' .
903            strtolower($this->_currentPackage['package']);
904        if (isset($dl->___uninstall_package_cache)) {
905            $badpackages = $dl->___uninstall_package_cache;
906            if (isset($badpackages[$memyselfandI]['warnings'])) {
907                foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
908                    $dl->log(0, $warning[0]);
909                }
910            }
911
912            if (isset($badpackages[$memyselfandI]['errors'])) {
913                foreach ($badpackages[$memyselfandI]['errors'] as $error) {
914                    if (is_array($error)) {
915                        $dl->log(0, $error[0]);
916                    } else {
917                        $dl->log(0, $error->getMessage());
918                    }
919                }
920
921                if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
922                    return $this->warning(
923                        'warning: %s should not be uninstalled, other installed packages depend ' .
924                        'on this package');
925                }
926
927                return $this->raiseError(
928                    '%s cannot be uninstalled, other installed packages depend on this package');
929            }
930
931            return true;
932        }
933
934        // first, list the immediate parents of each package to be uninstalled
935        $perpackagelist = array();
936        $allparents = array();
937        foreach ($params as $i => $param) {
938            $a = array(
939                'channel' => strtolower($param->getChannel()),
940                'package' => strtolower($param->getPackage())
941            );
942
943            $deps = $this->_dependencydb->getDependentPackages($a);
944            if ($deps) {
945                foreach ($deps as $d) {
946                    $pardeps = $this->_dependencydb->getDependencies($d);
947                    foreach ($pardeps as $dep) {
948                        if (strtolower($dep['dep']['channel']) == $a['channel'] &&
949                              strtolower($dep['dep']['name']) == $a['package']) {
950                            if (!isset($perpackagelist[$a['channel'] . '/' . $a['package']])) {
951                                $perpackagelist[$a['channel'] . '/' . $a['package']] = array();
952                            }
953                            $perpackagelist[$a['channel'] . '/' . $a['package']][]
954                                = array($d['channel'] . '/' . $d['package'], $dep);
955                            if (!isset($allparents[$d['channel'] . '/' . $d['package']])) {
956                                $allparents[$d['channel'] . '/' . $d['package']] = array();
957                            }
958                            if (!isset($allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']])) {
959                                $allparents[$d['channel'] . '/' . $d['package']][$a['channel'] . '/' . $a['package']] = array();
960                            }
961                            $allparents[$d['channel'] . '/' . $d['package']]
962                                       [$a['channel'] . '/' . $a['package']][]
963                                = array($d, $dep);
964                        }
965                    }
966                }
967            }
968        }
969
970        // next, remove any packages from the parents list that are not installed
971        $remove = array();
972        foreach ($allparents as $parent => $d1) {
973            foreach ($d1 as $d) {
974                if ($this->_registry->packageExists($d[0][0]['package'], $d[0][0]['channel'])) {
975                    continue;
976                }
977                $remove[$parent] = true;
978            }
979        }
980
981        // next remove any packages from the parents list that are not passed in for
982        // uninstallation
983        foreach ($allparents as $parent => $d1) {
984            foreach ($d1 as $d) {
985                foreach ($params as $param) {
986                    if (strtolower($param->getChannel()) == $d[0][0]['channel'] &&
987                          strtolower($param->getPackage()) == $d[0][0]['package']) {
988                        // found it
989                        continue 3;
990                    }
991                }
992                $remove[$parent] = true;
993            }
994        }
995
996        // remove all packages whose dependencies fail
997        // save which ones failed for error reporting
998        $badchildren = array();
999        do {
1000            $fail = false;
1001            foreach ($remove as $package => $unused) {
1002                if (!isset($allparents[$package])) {
1003                    continue;
1004                }
1005
1006                foreach ($allparents[$package] as $kid => $d1) {
1007                    foreach ($d1 as $depinfo) {
1008                        if ($depinfo[1]['type'] != 'optional') {
1009                            if (isset($badchildren[$kid])) {
1010                                continue;
1011                            }
1012                            $badchildren[$kid] = true;
1013                            $remove[$kid] = true;
1014                            $fail = true;
1015                            continue 2;
1016                        }
1017                    }
1018                }
1019                if ($fail) {
1020                    // start over, we removed some children
1021                    continue 2;
1022                }
1023            }
1024        } while ($fail);
1025
1026        // next, construct the list of packages that can't be uninstalled
1027        $badpackages = array();
1028        $save = $this->_currentPackage;
1029        foreach ($perpackagelist as $package => $packagedeps) {
1030            foreach ($packagedeps as $parent) {
1031                if (!isset($remove[$parent[0]])) {
1032                    continue;
1033                }
1034
1035                $packagename = $this->_registry->parsePackageName($parent[0]);
1036                $packagename['channel'] = $this->_registry->channelAlias($packagename['channel']);
1037                $pa = $this->_registry->getPackage($packagename['package'], $packagename['channel']);
1038                $packagename['package'] = $pa->getPackage();
1039                $this->_currentPackage = $packagename;
1040                // parent is not present in uninstall list, make sure we can actually
1041                // uninstall it (parent dep is optional)
1042                $parentname['channel'] = $this->_registry->channelAlias($parent[1]['dep']['channel']);
1043                $pa = $this->_registry->getPackage($parent[1]['dep']['name'], $parent[1]['dep']['channel']);
1044                $parentname['package'] = $pa->getPackage();
1045                $parent[1]['dep']['package'] = $parentname['package'];
1046                $parent[1]['dep']['channel'] = $parentname['channel'];
1047                if ($parent[1]['type'] == 'optional') {
1048                    $test = $this->_validatePackageUninstall($parent[1]['dep'], false, $dl);
1049                    if ($test !== true) {
1050                        $badpackages[$package]['warnings'][] = $test;
1051                    }
1052                } else {
1053                    $test = $this->_validatePackageUninstall($parent[1]['dep'], true, $dl);
1054                    if ($test !== true) {
1055                        $badpackages[$package]['errors'][] = $test;
1056                    }
1057                }
1058            }
1059        }
1060
1061        $this->_currentPackage          = $save;
1062        $dl->___uninstall_package_cache = $badpackages;
1063        if (isset($badpackages[$memyselfandI])) {
1064            if (isset($badpackages[$memyselfandI]['warnings'])) {
1065                foreach ($badpackages[$memyselfandI]['warnings'] as $warning) {
1066                    $dl->log(0, $warning[0]);
1067                }
1068            }
1069
1070            if (isset($badpackages[$memyselfandI]['errors'])) {
1071                foreach ($badpackages[$memyselfandI]['errors'] as $error) {
1072                    if (is_array($error)) {
1073                        $dl->log(0, $error[0]);
1074                    } else {
1075                        $dl->log(0, $error->getMessage());
1076                    }
1077                }
1078
1079                if (isset($this->_options['nodeps']) || isset($this->_options['force'])) {
1080                    return $this->warning(
1081                        'warning: %s should not be uninstalled, other installed packages depend ' .
1082                        'on this package');
1083                }
1084
1085                return $this->raiseError(
1086                    '%s cannot be uninstalled, other installed packages depend on this package');
1087            }
1088        }
1089
1090        return true;
1091    }
1092
1093    function _validatePackageUninstall($dep, $required, $dl)
1094    {
1095        $depname = $this->_registry->parsedPackageNameToString($dep, true);
1096        $version = $this->_registry->packageinfo($dep['package'], 'version', $dep['channel']);
1097        if (!$version) {
1098            return true;
1099        }
1100
1101        $extra = self::_getExtraString($dep);
1102        if (isset($dep['exclude']) && !is_array($dep['exclude'])) {
1103            $dep['exclude'] = array($dep['exclude']);
1104        }
1105
1106        if (isset($dep['conflicts'])) {
1107            return true; // uninstall OK - these packages conflict (probably installed with --force)
1108        }
1109
1110        if (!isset($dep['min']) && !isset($dep['max'])) {
1111            if (!$required) {
1112                return $this->warning('"' . $depname . '" can be optionally used by ' .
1113                        'installed package %s' . $extra);
1114            }
1115
1116            if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
1117                return $this->raiseError('"' . $depname . '" is required by ' .
1118                    'installed package %s' . $extra);
1119            }
1120
1121            return $this->warning('warning: "' . $depname . '" is required by ' .
1122                'installed package %s' . $extra);
1123        }
1124
1125        $fail = false;
1126        if (isset($dep['min']) && version_compare($version, $dep['min'], '>=')) {
1127            $fail = true;
1128        }
1129
1130        if (isset($dep['max']) && version_compare($version, $dep['max'], '<=')) {
1131            $fail = true;
1132        }
1133
1134        // we re-use this variable, preserve the original value
1135        $saverequired = $required;
1136        if (!$required) {
1137            return $this->warning($depname . $extra . ' can be optionally used by installed package' .
1138                    ' "%s"');
1139        }
1140
1141        if (!isset($this->_options['nodeps']) && !isset($this->_options['force'])) {
1142            return $this->raiseError($depname . $extra . ' is required by installed package' .
1143                ' "%s"');
1144        }
1145
1146        return $this->raiseError('warning: ' . $depname . $extra .
1147            ' is required by installed package "%s"');
1148    }
1149
1150    /**
1151     * validate a downloaded package against installed packages
1152     *
1153     * As of PEAR 1.4.3, this will only validate
1154     *
1155     * @param array|PEAR_Downloader_Package|PEAR_PackageFile_v1|PEAR_PackageFile_v2
1156     *              $pkg package identifier (either
1157     *                   array('package' => blah, 'channel' => blah) or an array with
1158     *                   index 'info' referencing an object)
1159     * @param PEAR_Downloader $dl
1160     * @param array $params full list of packages to install
1161     * @return true|PEAR_Error
1162     */
1163    function validatePackage($pkg, &$dl, $params = array())
1164    {
1165        if (is_array($pkg) && isset($pkg['info'])) {
1166            $deps = $this->_dependencydb->getDependentPackageDependencies($pkg['info']);
1167        } else {
1168            $deps = $this->_dependencydb->getDependentPackageDependencies($pkg);
1169        }
1170
1171        $fail = false;
1172        if ($deps) {
1173            if (!class_exists('PEAR_Downloader_Package')) {
1174                require_once 'PEAR/Downloader/Package.php';
1175            }
1176
1177            $dp = new PEAR_Downloader_Package($dl);
1178            if (is_object($pkg)) {
1179                $dp->setPackageFile($pkg);
1180            } else {
1181                $dp->setDownloadURL($pkg);
1182            }
1183
1184            PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
1185            foreach ($deps as $channel => $info) {
1186                foreach ($info as $package => $ds) {
1187                    foreach ($params as $packd) {
1188                        if (strtolower($packd->getPackage()) == strtolower($package) &&
1189                              $packd->getChannel() == $channel) {
1190                            $dl->log(3, 'skipping installed package check of "' .
1191                                        $this->_registry->parsedPackageNameToString(
1192                                            array('channel' => $channel, 'package' => $package),
1193                                            true) .
1194                                        '", version "' . $packd->getVersion() . '" will be ' .
1195                                        'downloaded and installed');
1196                            continue 2; // jump to next package
1197                        }
1198                    }
1199
1200                    foreach ($ds as $d) {
1201                        $checker = new PEAR_Dependency2($this->_config, $this->_options,
1202                            array('channel' => $channel, 'package' => $package), $this->_state);
1203                        $dep = $d['dep'];
1204                        $required = $d['type'] == 'required';
1205                        $ret = $checker->_validatePackageDownload($dep, $required, array(&$dp));
1206                        if (is_array($ret)) {
1207                            $dl->log(0, $ret[0]);
1208                        } elseif (PEAR::isError($ret)) {
1209                            $dl->log(0, $ret->getMessage());
1210                            $fail = true;
1211                        }
1212                    }
1213                }
1214            }
1215            PEAR::popErrorHandling();
1216        }
1217
1218        if ($fail) {
1219            return $this->raiseError(
1220                '%s cannot be installed, conflicts with installed packages');
1221        }
1222
1223        return true;
1224    }
1225
1226    /**
1227     * validate a package.xml 1.0 dependency
1228     */
1229    function validateDependency1($dep, $params = array())
1230    {
1231        if (!isset($dep['optional'])) {
1232            $dep['optional'] = 'no';
1233        }
1234
1235        list($newdep, $type) = self::normalizeDep($dep);
1236        if (!$newdep) {
1237            return $this->raiseError("Invalid Dependency");
1238        }
1239
1240        if (method_exists($this, "validate{$type}Dependency")) {
1241            return $this->{"validate{$type}Dependency"}($newdep, $dep['optional'] == 'no',
1242                $params, true);
1243        }
1244    }
1245
1246    /**
1247     * Convert a 1.0 dep into a 2.0 dep
1248     */
1249    static function normalizeDep($dep)
1250    {
1251        $types = array(
1252            'pkg' => 'Package',
1253            'ext' => 'Extension',
1254            'os' => 'Os',
1255            'php' => 'Php'
1256        );
1257
1258        if (!isset($types[$dep['type']])) {
1259            return array(false, false);
1260        }
1261
1262        $type = $types[$dep['type']];
1263
1264        $newdep = array();
1265        switch ($type) {
1266            case 'Package' :
1267                $newdep['channel'] = 'pear.php.net';
1268            case 'Extension' :
1269            case 'Os' :
1270                $newdep['name'] = $dep['name'];
1271            break;
1272        }
1273
1274        $dep['rel'] = PEAR_Dependency2::signOperator($dep['rel']);
1275        switch ($dep['rel']) {
1276            case 'has' :
1277                return array($newdep, $type);
1278            break;
1279            case 'not' :
1280                $newdep['conflicts'] = true;
1281            break;
1282            case '>=' :
1283            case '>' :
1284                $newdep['min'] = $dep['version'];
1285                if ($dep['rel'] == '>') {
1286                    $newdep['exclude'] = $dep['version'];
1287                }
1288            break;
1289            case '<=' :
1290            case '<' :
1291                $newdep['max'] = $dep['version'];
1292                if ($dep['rel'] == '<') {
1293                    $newdep['exclude'] = $dep['version'];
1294                }
1295            break;
1296            case 'ne' :
1297            case '!=' :
1298                $newdep['min'] = '0';
1299                $newdep['max'] = '100000';
1300                $newdep['exclude'] = $dep['version'];
1301            break;
1302            case '==' :
1303                $newdep['min'] = $dep['version'];
1304                $newdep['max'] = $dep['version'];
1305            break;
1306        }
1307        if ($type == 'Php') {
1308            if (!isset($newdep['min'])) {
1309                $newdep['min'] = '4.4.0';
1310            }
1311
1312            if (!isset($newdep['max'])) {
1313                $newdep['max'] = '6.0.0';
1314            }
1315        }
1316        return array($newdep, $type);
1317    }
1318
1319    /**
1320     * Converts text comparing operators to them sign equivalents
1321     *
1322     * Example: 'ge' to '>='
1323     *
1324     * @access public
1325     * @param  string Operator
1326     * @return string Sign equivalent
1327     */
1328    static function signOperator($operator)
1329    {
1330        switch($operator) {
1331            case 'lt': return '<';
1332            case 'le': return '<=';
1333            case 'gt': return '>';
1334            case 'ge': return '>=';
1335            case 'eq': return '==';
1336            case 'ne': return '!=';
1337            default:
1338                return $operator;
1339        }
1340    }
1341
1342    function raiseError($msg)
1343    {
1344        if (isset($this->_options['ignore-errors'])) {
1345            return $this->warning($msg);
1346        }
1347
1348        return PEAR::raiseError(sprintf($msg, $this->_registry->parsedPackageNameToString(
1349            $this->_currentPackage, true)));
1350    }
1351
1352    function warning($msg)
1353    {
1354        return array(sprintf($msg, $this->_registry->parsedPackageNameToString(
1355            $this->_currentPackage, true)));
1356    }
1357}
1358