Validate.php 22 KB

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