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