1<?php 2/** 3 * PEAR_Validate 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 * Constants for install stage 17 */ 18define('PEAR_VALIDATE_INSTALLING', 1); 19define('PEAR_VALIDATE_UNINSTALLING', 2); // this is not bit-mapped like the others 20define('PEAR_VALIDATE_NORMAL', 3); 21define('PEAR_VALIDATE_DOWNLOADING', 4); // this is not bit-mapped like the others 22define('PEAR_VALIDATE_PACKAGING', 7); 23/**#@-*/ 24require_once 'PEAR/Common.php'; 25require_once 'PEAR/Validator/PECL.php'; 26 27/** 28 * Validation class for package.xml - channel-level advanced validation 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: @package_version@ 35 * @link http://pear.php.net/package/PEAR 36 * @since Class available since Release 1.4.0a1 37 */ 38class PEAR_Validate 39{ 40 var $packageregex = _PEAR_COMMON_PACKAGE_NAME_PREG; 41 /** 42 * @var PEAR_PackageFile_v1|PEAR_PackageFile_v2 43 */ 44 var $_packagexml; 45 /** 46 * @var int one of the PEAR_VALIDATE_* constants 47 */ 48 var $_state = PEAR_VALIDATE_NORMAL; 49 /** 50 * Format: ('error' => array('field' => name, 'reason' => reason), 'warning' => same) 51 * @var array 52 * @access private 53 */ 54 var $_failures = array('error' => array(), 'warning' => array()); 55 56 /** 57 * Override this method to handle validation of normal package names 58 * @param string 59 * @return bool 60 * @access protected 61 */ 62 function _validPackageName($name) 63 { 64 return (bool) preg_match('/^' . $this->packageregex . '\\z/', $name); 65 } 66 67 /** 68 * @param string package name to validate 69 * @param string name of channel-specific validation package 70 * @final 71 */ 72 function validPackageName($name, $validatepackagename = false) 73 { 74 if ($validatepackagename) { 75 if (strtolower($name) == strtolower($validatepackagename)) { 76 return (bool) preg_match('/^[a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)*\\z/', $name); 77 } 78 } 79 return $this->_validPackageName($name); 80 } 81 82 /** 83 * This validates a bundle name, and bundle names must conform 84 * to the PEAR naming convention, so the method is final and static. 85 * @param string 86 * @final 87 */ 88 public static function validGroupName($name) 89 { 90 return (bool) preg_match('/^' . _PEAR_COMMON_PACKAGE_NAME_PREG . '\\z/', $name); 91 } 92 93 /** 94 * Determine whether $state represents a valid stability level 95 * @param string 96 * @return bool 97 * @final 98 */ 99 public static function validState($state) 100 { 101 return in_array($state, array('snapshot', 'devel', 'alpha', 'beta', 'stable')); 102 } 103 104 /** 105 * Get a list of valid stability levels 106 * @return array 107 * @final 108 */ 109 public static function getValidStates() 110 { 111 return array('snapshot', 'devel', 'alpha', 'beta', 'stable'); 112 } 113 114 /** 115 * Determine whether a version is a properly formatted version number that can be used 116 * by version_compare 117 * @param string 118 * @return bool 119 * @final 120 */ 121 public static function validVersion($ver) 122 { 123 return (bool) preg_match(PEAR_COMMON_PACKAGE_VERSION_PREG, $ver); 124 } 125 126 /** 127 * @param PEAR_PackageFile_v1|PEAR_PackageFile_v2 128 */ 129 function setPackageFile(&$pf) 130 { 131 $this->_packagexml = &$pf; 132 } 133 134 /** 135 * @access private 136 */ 137 function _addFailure($field, $reason) 138 { 139 $this->_failures['errors'][] = array('field' => $field, 'reason' => $reason); 140 } 141 142 /** 143 * @access private 144 */ 145 function _addWarning($field, $reason) 146 { 147 $this->_failures['warnings'][] = array('field' => $field, 'reason' => $reason); 148 } 149 150 function getFailures() 151 { 152 $failures = $this->_failures; 153 $this->_failures = array('warnings' => array(), 'errors' => array()); 154 return $failures; 155 } 156 157 /** 158 * @param int one of the PEAR_VALIDATE_* constants 159 */ 160 function validate($state = null) 161 { 162 if (!isset($this->_packagexml)) { 163 return false; 164 } 165 if ($state !== null) { 166 $this->_state = $state; 167 } 168 $this->_failures = array('warnings' => array(), 'errors' => array()); 169 $this->validatePackageName(); 170 $this->validateVersion(); 171 $this->validateMaintainers(); 172 $this->validateDate(); 173 $this->validateSummary(); 174 $this->validateDescription(); 175 $this->validateLicense(); 176 $this->validateNotes(); 177 if ($this->_packagexml->getPackagexmlVersion() == '1.0') { 178 $this->validateState(); 179 $this->validateFilelist(); 180 } elseif ($this->_packagexml->getPackagexmlVersion() == '2.0' || 181 $this->_packagexml->getPackagexmlVersion() == '2.1') { 182 $this->validateTime(); 183 $this->validateStability(); 184 $this->validateDeps(); 185 $this->validateMainFilelist(); 186 $this->validateReleaseFilelist(); 187 //$this->validateGlobalTasks(); 188 $this->validateChangelog(); 189 } 190 return !((bool) count($this->_failures['errors'])); 191 } 192 193 /** 194 * @access protected 195 */ 196 function validatePackageName() 197 { 198 if ($this->_state == PEAR_VALIDATE_PACKAGING || 199 $this->_state == PEAR_VALIDATE_NORMAL) { 200 if (($this->_packagexml->getPackagexmlVersion() == '2.0' || 201 $this->_packagexml->getPackagexmlVersion() == '2.1') && 202 $this->_packagexml->getExtends()) { 203 $version = $this->_packagexml->getVersion() . ''; 204 $name = $this->_packagexml->getPackage(); 205 $a = explode('.', $version); 206 $test = array_shift($a); 207 if ($test == '0') { 208 return true; 209 } 210 $vlen = strlen($test); 211 $majver = substr($name, strlen($name) - $vlen); 212 while ($majver && !is_numeric($majver{0})) { 213 $majver = substr($majver, 1); 214 } 215 if ($majver != $test) { 216 $this->_addWarning('package', "package $name extends package " . 217 $this->_packagexml->getExtends() . ' and so the name should ' . 218 'have a postfix equal to the major version like "' . 219 $this->_packagexml->getExtends() . $test . '"'); 220 return true; 221 } elseif (substr($name, 0, strlen($name) - $vlen) != 222 $this->_packagexml->getExtends()) { 223 $this->_addWarning('package', "package $name extends package " . 224 $this->_packagexml->getExtends() . ' and so the name must ' . 225 'be an extension like "' . $this->_packagexml->getExtends() . 226 $test . '"'); 227 return true; 228 } 229 } 230 } 231 if (!$this->validPackageName($this->_packagexml->getPackage())) { 232 $this->_addFailure('name', 'package name "' . 233 $this->_packagexml->getPackage() . '" is invalid'); 234 return false; 235 } 236 } 237 238 /** 239 * @access protected 240 */ 241 function validateVersion() 242 { 243 if ($this->_state != PEAR_VALIDATE_PACKAGING) { 244 if (!$this->validVersion($this->_packagexml->getVersion())) { 245 $this->_addFailure('version', 246 'Invalid version number "' . $this->_packagexml->getVersion() . '"'); 247 } 248 return false; 249 } 250 $version = $this->_packagexml->getVersion(); 251 $versioncomponents = explode('.', $version); 252 if (count($versioncomponents) != 3) { 253 $this->_addWarning('version', 254 'A version number should have 3 decimals (x.y.z)'); 255 return true; 256 } 257 $name = $this->_packagexml->getPackage(); 258 // version must be based upon state 259 switch ($this->_packagexml->getState()) { 260 case 'snapshot' : 261 return true; 262 case 'devel' : 263 if ($versioncomponents[0] . 'a' == '0a') { 264 return true; 265 } 266 if ($versioncomponents[0] == 0) { 267 $versioncomponents[0] = '0'; 268 $this->_addWarning('version', 269 'version "' . $version . '" should be "' . 270 implode('.' ,$versioncomponents) . '"'); 271 } else { 272 $this->_addWarning('version', 273 'packages with devel stability must be < version 1.0.0'); 274 } 275 return true; 276 break; 277 case 'alpha' : 278 case 'beta' : 279 // check for a package that extends a package, 280 // like Foo and Foo2 281 if ($this->_state == PEAR_VALIDATE_PACKAGING) { 282 if (substr($versioncomponents[2], 1, 2) == 'rc') { 283 $this->_addFailure('version', 'Release Candidate versions ' . 284 'must have capital RC, not lower-case rc'); 285 return false; 286 } 287 } 288 if (!$this->_packagexml->getExtends()) { 289 if ($versioncomponents[0] == '1') { 290 if ($versioncomponents[2]{0} == '0') { 291 if ($versioncomponents[2] == '0') { 292 // version 1.*.0000 293 $this->_addWarning('version', 294 'version 1.' . $versioncomponents[1] . 295 '.0 probably should not be alpha or beta'); 296 return true; 297 } elseif (strlen($versioncomponents[2]) > 1) { 298 // version 1.*.0RC1 or 1.*.0beta24 etc. 299 return true; 300 } else { 301 // version 1.*.0 302 $this->_addWarning('version', 303 'version 1.' . $versioncomponents[1] . 304 '.0 probably should not be alpha or beta'); 305 return true; 306 } 307 } else { 308 $this->_addWarning('version', 309 'bugfix versions (1.3.x where x > 0) probably should ' . 310 'not be alpha or beta'); 311 return true; 312 } 313 } elseif ($versioncomponents[0] != '0') { 314 $this->_addWarning('version', 315 'major versions greater than 1 are not allowed for packages ' . 316 'without an <extends> tag or an identical postfix (foo2 v2.0.0)'); 317 return true; 318 } 319 if ($versioncomponents[0] . 'a' == '0a') { 320 return true; 321 } 322 if ($versioncomponents[0] == 0) { 323 $versioncomponents[0] = '0'; 324 $this->_addWarning('version', 325 'version "' . $version . '" should be "' . 326 implode('.' ,$versioncomponents) . '"'); 327 } 328 } else { 329 $vlen = strlen($versioncomponents[0] . ''); 330 $majver = substr($name, strlen($name) - $vlen); 331 while ($majver && !is_numeric($majver{0})) { 332 $majver = substr($majver, 1); 333 } 334 if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { 335 $this->_addWarning('version', 'first version number "' . 336 $versioncomponents[0] . '" must match the postfix of ' . 337 'package name "' . $name . '" (' . 338 $majver . ')'); 339 return true; 340 } 341 if ($versioncomponents[0] == $majver) { 342 if ($versioncomponents[2]{0} == '0') { 343 if ($versioncomponents[2] == '0') { 344 // version 2.*.0000 345 $this->_addWarning('version', 346 "version $majver." . $versioncomponents[1] . 347 '.0 probably should not be alpha or beta'); 348 return false; 349 } elseif (strlen($versioncomponents[2]) > 1) { 350 // version 2.*.0RC1 or 2.*.0beta24 etc. 351 return true; 352 } else { 353 // version 2.*.0 354 $this->_addWarning('version', 355 "version $majver." . $versioncomponents[1] . 356 '.0 cannot be alpha or beta'); 357 return true; 358 } 359 } else { 360 $this->_addWarning('version', 361 "bugfix versions ($majver.x.y where y > 0) should " . 362 'not be alpha or beta'); 363 return true; 364 } 365 } elseif ($versioncomponents[0] != '0') { 366 $this->_addWarning('version', 367 "only versions 0.x.y and $majver.x.y are allowed for alpha/beta releases"); 368 return true; 369 } 370 if ($versioncomponents[0] . 'a' == '0a') { 371 return true; 372 } 373 if ($versioncomponents[0] == 0) { 374 $versioncomponents[0] = '0'; 375 $this->_addWarning('version', 376 'version "' . $version . '" should be "' . 377 implode('.' ,$versioncomponents) . '"'); 378 } 379 } 380 return true; 381 break; 382 case 'stable' : 383 if ($versioncomponents[0] == '0') { 384 $this->_addWarning('version', 'versions less than 1.0.0 cannot ' . 385 'be stable'); 386 return true; 387 } 388 if (!is_numeric($versioncomponents[2])) { 389 if (preg_match('/\d+(rc|a|alpha|b|beta)\d*/i', 390 $versioncomponents[2])) { 391 $this->_addWarning('version', 'version "' . $version . '" or any ' . 392 'RC/beta/alpha version cannot be stable'); 393 return true; 394 } 395 } 396 // check for a package that extends a package, 397 // like Foo and Foo2 398 if ($this->_packagexml->getExtends()) { 399 $vlen = strlen($versioncomponents[0] . ''); 400 $majver = substr($name, strlen($name) - $vlen); 401 while ($majver && !is_numeric($majver{0})) { 402 $majver = substr($majver, 1); 403 } 404 if (($versioncomponents[0] != 0) && $majver != $versioncomponents[0]) { 405 $this->_addWarning('version', 'first version number "' . 406 $versioncomponents[0] . '" must match the postfix of ' . 407 'package name "' . $name . '" (' . 408 $majver . ')'); 409 return true; 410 } 411 } elseif ($versioncomponents[0] > 1) { 412 $this->_addWarning('version', 'major version x in x.y.z may not be greater than ' . 413 '1 for any package that does not have an <extends> tag'); 414 } 415 return true; 416 break; 417 default : 418 return false; 419 break; 420 } 421 } 422 423 /** 424 * @access protected 425 */ 426 function validateMaintainers() 427 { 428 // maintainers can only be truly validated server-side for most channels 429 // but allow this customization for those who wish it 430 return true; 431 } 432 433 /** 434 * @access protected 435 */ 436 function validateDate() 437 { 438 if ($this->_state == PEAR_VALIDATE_NORMAL || 439 $this->_state == PEAR_VALIDATE_PACKAGING) { 440 441 if (!preg_match('/(\d\d\d\d)\-(\d\d)\-(\d\d)/', 442 $this->_packagexml->getDate(), $res) || 443 count($res) < 4 444 || !checkdate($res[2], $res[3], $res[1]) 445 ) { 446 $this->_addFailure('date', 'invalid release date "' . 447 $this->_packagexml->getDate() . '"'); 448 return false; 449 } 450 451 if ($this->_state == PEAR_VALIDATE_PACKAGING && 452 $this->_packagexml->getDate() != date('Y-m-d')) { 453 $this->_addWarning('date', 'Release Date "' . 454 $this->_packagexml->getDate() . '" is not today'); 455 } 456 } 457 return true; 458 } 459 460 /** 461 * @access protected 462 */ 463 function validateTime() 464 { 465 if (!$this->_packagexml->getTime()) { 466 // default of no time value set 467 return true; 468 } 469 470 // packager automatically sets time, so only validate if pear validate is called 471 if ($this->_state = PEAR_VALIDATE_NORMAL) { 472 if (!preg_match('/\d\d:\d\d:\d\d/', 473 $this->_packagexml->getTime())) { 474 $this->_addFailure('time', 'invalid release time "' . 475 $this->_packagexml->getTime() . '"'); 476 return false; 477 } 478 479 $result = preg_match('|\d{2}\:\d{2}\:\d{2}|', $this->_packagexml->getTime(), $matches); 480 if ($result === false || empty($matches)) { 481 $this->_addFailure('time', 'invalid release time "' . 482 $this->_packagexml->getTime() . '"'); 483 return false; 484 } 485 } 486 487 return true; 488 } 489 490 /** 491 * @access protected 492 */ 493 function validateState() 494 { 495 // this is the closest to "final" php4 can get 496 if (!PEAR_Validate::validState($this->_packagexml->getState())) { 497 if (strtolower($this->_packagexml->getState() == 'rc')) { 498 $this->_addFailure('state', 'RC is not a state, it is a version ' . 499 'postfix, use ' . $this->_packagexml->getVersion() . 'RC1, state beta'); 500 } 501 $this->_addFailure('state', 'invalid release state "' . 502 $this->_packagexml->getState() . '", must be one of: ' . 503 implode(', ', PEAR_Validate::getValidStates())); 504 return false; 505 } 506 return true; 507 } 508 509 /** 510 * @access protected 511 */ 512 function validateStability() 513 { 514 $ret = true; 515 $packagestability = $this->_packagexml->getState(); 516 $apistability = $this->_packagexml->getState('api'); 517 if (!PEAR_Validate::validState($packagestability)) { 518 $this->_addFailure('state', 'invalid release stability "' . 519 $this->_packagexml->getState() . '", must be one of: ' . 520 implode(', ', PEAR_Validate::getValidStates())); 521 $ret = false; 522 } 523 $apistates = PEAR_Validate::getValidStates(); 524 array_shift($apistates); // snapshot is not allowed 525 if (!in_array($apistability, $apistates)) { 526 $this->_addFailure('state', 'invalid API stability "' . 527 $this->_packagexml->getState('api') . '", must be one of: ' . 528 implode(', ', $apistates)); 529 $ret = false; 530 } 531 return $ret; 532 } 533 534 /** 535 * @access protected 536 */ 537 function validateSummary() 538 { 539 return true; 540 } 541 542 /** 543 * @access protected 544 */ 545 function validateDescription() 546 { 547 return true; 548 } 549 550 /** 551 * @access protected 552 */ 553 function validateLicense() 554 { 555 return true; 556 } 557 558 /** 559 * @access protected 560 */ 561 function validateNotes() 562 { 563 return true; 564 } 565 566 /** 567 * for package.xml 2.0 only - channels can't use package.xml 1.0 568 * @access protected 569 */ 570 function validateDependencies() 571 { 572 return true; 573 } 574 575 /** 576 * for package.xml 1.0 only 577 * @access private 578 */ 579 function _validateFilelist() 580 { 581 return true; // placeholder for now 582 } 583 584 /** 585 * for package.xml 2.0 only 586 * @access protected 587 */ 588 function validateMainFilelist() 589 { 590 return true; // placeholder for now 591 } 592 593 /** 594 * for package.xml 2.0 only 595 * @access protected 596 */ 597 function validateReleaseFilelist() 598 { 599 return true; // placeholder for now 600 } 601 602 /** 603 * @access protected 604 */ 605 function validateChangelog() 606 { 607 return true; 608 } 609 610 /** 611 * @access protected 612 */ 613 function validateFilelist() 614 { 615 return true; 616 } 617 618 /** 619 * @access protected 620 */ 621 function validateDeps() 622 { 623 return true; 624 } 625}