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