Updated PEAR and PEAR packages.
[timetracker.git] / WEB-INF / lib / pear / PEAR / Dependency2.php
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  */
19 require_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: 1.10.1
34  * @link       http://pear.php.net/package/PEAR
35  * @since      Class available since Release 1.4.0a1
36  */
37 class 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     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  = $this->_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   = $this->_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 '1.10.1';
544     }
545
546     function validatePearinstallerDependency($dep)
547     {
548         $pearversion = $this->getPEARVersion();
549         $extra = $this->_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 = $this->_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 = $this->_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) = $this->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     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     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 }