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