PageRenderTime 115ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/php/PEAR/PackageFile/v2/Validator.php

https://bitbucket.org/adarshj/convenient_website
PHP | 2154 lines | 2063 code | 31 blank | 60 comment | 291 complexity | 4bb188500dcecfca3ee085572a6f88d2 MD5 | raw file
Possible License(s): Apache-2.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, BSD-2-Clause, GPL-2.0, LGPL-3.0
  1. <?php
  2. /**
  3. * PEAR_PackageFile_v2, package.xml version 2.0, read/write version
  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: Validator.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.0a8
  15. */
  16. /**
  17. * Private validation class used by PEAR_PackageFile_v2 - do not use directly, its
  18. * sole purpose is to split up the PEAR/PackageFile/v2.php file to make it smaller
  19. * @category pear
  20. * @package PEAR
  21. * @author Greg Beaver <cellog@php.net>
  22. * @copyright 1997-2009 The Authors
  23. * @license http://opensource.org/licenses/bsd-license.php New BSD License
  24. * @version Release: 1.9.4
  25. * @link http://pear.php.net/package/PEAR
  26. * @since Class available since Release 1.4.0a8
  27. * @access private
  28. */
  29. class PEAR_PackageFile_v2_Validator
  30. {
  31. /**
  32. * @var array
  33. */
  34. var $_packageInfo;
  35. /**
  36. * @var PEAR_PackageFile_v2
  37. */
  38. var $_pf;
  39. /**
  40. * @var PEAR_ErrorStack
  41. */
  42. var $_stack;
  43. /**
  44. * @var int
  45. */
  46. var $_isValid = 0;
  47. /**
  48. * @var int
  49. */
  50. var $_filesValid = 0;
  51. /**
  52. * @var int
  53. */
  54. var $_curState = 0;
  55. /**
  56. * @param PEAR_PackageFile_v2
  57. * @param int
  58. */
  59. function validate(&$pf, $state = PEAR_VALIDATE_NORMAL)
  60. {
  61. $this->_pf = &$pf;
  62. $this->_curState = $state;
  63. $this->_packageInfo = $this->_pf->getArray();
  64. $this->_isValid = $this->_pf->_isValid;
  65. $this->_filesValid = $this->_pf->_filesValid;
  66. $this->_stack = &$pf->_stack;
  67. $this->_stack->getErrors(true);
  68. if (($this->_isValid & $state) == $state) {
  69. return true;
  70. }
  71. if (!isset($this->_packageInfo) || !is_array($this->_packageInfo)) {
  72. return false;
  73. }
  74. if (!isset($this->_packageInfo['attribs']['version']) ||
  75. ($this->_packageInfo['attribs']['version'] != '2.0' &&
  76. $this->_packageInfo['attribs']['version'] != '2.1')
  77. ) {
  78. $this->_noPackageVersion();
  79. }
  80. $structure =
  81. array(
  82. 'name',
  83. 'channel|uri',
  84. '*extends', // can't be multiple, but this works fine
  85. 'summary',
  86. 'description',
  87. '+lead', // these all need content checks
  88. '*developer',
  89. '*contributor',
  90. '*helper',
  91. 'date',
  92. '*time',
  93. 'version',
  94. 'stability',
  95. 'license->?uri->?filesource',
  96. 'notes',
  97. 'contents', //special validation needed
  98. '*compatible',
  99. 'dependencies', //special validation needed
  100. '*usesrole',
  101. '*usestask', // reserve these for 1.4.0a1 to implement
  102. // this will allow a package.xml to gracefully say it
  103. // needs a certain package installed in order to implement a role or task
  104. '*providesextension',
  105. '*srcpackage|*srcuri',
  106. '+phprelease|+extsrcrelease|+extbinrelease|' .
  107. '+zendextsrcrelease|+zendextbinrelease|bundle', //special validation needed
  108. '*changelog',
  109. );
  110. $test = $this->_packageInfo;
  111. if (isset($test['dependencies']) &&
  112. isset($test['dependencies']['required']) &&
  113. isset($test['dependencies']['required']['pearinstaller']) &&
  114. isset($test['dependencies']['required']['pearinstaller']['min']) &&
  115. version_compare('1.9.4',
  116. $test['dependencies']['required']['pearinstaller']['min'], '<')
  117. ) {
  118. $this->_pearVersionTooLow($test['dependencies']['required']['pearinstaller']['min']);
  119. return false;
  120. }
  121. // ignore post-installation array fields
  122. if (array_key_exists('filelist', $test)) {
  123. unset($test['filelist']);
  124. }
  125. if (array_key_exists('_lastmodified', $test)) {
  126. unset($test['_lastmodified']);
  127. }
  128. if (array_key_exists('#binarypackage', $test)) {
  129. unset($test['#binarypackage']);
  130. }
  131. if (array_key_exists('old', $test)) {
  132. unset($test['old']);
  133. }
  134. if (array_key_exists('_lastversion', $test)) {
  135. unset($test['_lastversion']);
  136. }
  137. if (!$this->_stupidSchemaValidate($structure, $test, '<package>')) {
  138. return false;
  139. }
  140. if (empty($this->_packageInfo['name'])) {
  141. $this->_tagCannotBeEmpty('name');
  142. }
  143. $test = isset($this->_packageInfo['uri']) ? 'uri' :'channel';
  144. if (empty($this->_packageInfo[$test])) {
  145. $this->_tagCannotBeEmpty($test);
  146. }
  147. if (is_array($this->_packageInfo['license']) &&
  148. (!isset($this->_packageInfo['license']['_content']) ||
  149. empty($this->_packageInfo['license']['_content']))) {
  150. $this->_tagCannotBeEmpty('license');
  151. } elseif (empty($this->_packageInfo['license'])) {
  152. $this->_tagCannotBeEmpty('license');
  153. }
  154. if (empty($this->_packageInfo['summary'])) {
  155. $this->_tagCannotBeEmpty('summary');
  156. }
  157. if (empty($this->_packageInfo['description'])) {
  158. $this->_tagCannotBeEmpty('description');
  159. }
  160. if (empty($this->_packageInfo['date'])) {
  161. $this->_tagCannotBeEmpty('date');
  162. }
  163. if (empty($this->_packageInfo['notes'])) {
  164. $this->_tagCannotBeEmpty('notes');
  165. }
  166. if (isset($this->_packageInfo['time']) && empty($this->_packageInfo['time'])) {
  167. $this->_tagCannotBeEmpty('time');
  168. }
  169. if (isset($this->_packageInfo['dependencies'])) {
  170. $this->_validateDependencies();
  171. }
  172. if (isset($this->_packageInfo['compatible'])) {
  173. $this->_validateCompatible();
  174. }
  175. if (!isset($this->_packageInfo['bundle'])) {
  176. if (empty($this->_packageInfo['contents'])) {
  177. $this->_tagCannotBeEmpty('contents');
  178. }
  179. if (!isset($this->_packageInfo['contents']['dir'])) {
  180. $this->_filelistMustContainDir('contents');
  181. return false;
  182. }
  183. if (isset($this->_packageInfo['contents']['file'])) {
  184. $this->_filelistCannotContainFile('contents');
  185. return false;
  186. }
  187. }
  188. $this->_validateMaintainers();
  189. $this->_validateStabilityVersion();
  190. $fail = false;
  191. if (array_key_exists('usesrole', $this->_packageInfo)) {
  192. $roles = $this->_packageInfo['usesrole'];
  193. if (!is_array($roles) || !isset($roles[0])) {
  194. $roles = array($roles);
  195. }
  196. foreach ($roles as $role) {
  197. if (!isset($role['role'])) {
  198. $this->_usesroletaskMustHaveRoleTask('usesrole', 'role');
  199. $fail = true;
  200. } else {
  201. if (!isset($role['channel'])) {
  202. if (!isset($role['uri'])) {
  203. $this->_usesroletaskMustHaveChannelOrUri($role['role'], 'usesrole');
  204. $fail = true;
  205. }
  206. } elseif (!isset($role['package'])) {
  207. $this->_usesroletaskMustHavePackage($role['role'], 'usesrole');
  208. $fail = true;
  209. }
  210. }
  211. }
  212. }
  213. if (array_key_exists('usestask', $this->_packageInfo)) {
  214. $roles = $this->_packageInfo['usestask'];
  215. if (!is_array($roles) || !isset($roles[0])) {
  216. $roles = array($roles);
  217. }
  218. foreach ($roles as $role) {
  219. if (!isset($role['task'])) {
  220. $this->_usesroletaskMustHaveRoleTask('usestask', 'task');
  221. $fail = true;
  222. } else {
  223. if (!isset($role['channel'])) {
  224. if (!isset($role['uri'])) {
  225. $this->_usesroletaskMustHaveChannelOrUri($role['task'], 'usestask');
  226. $fail = true;
  227. }
  228. } elseif (!isset($role['package'])) {
  229. $this->_usesroletaskMustHavePackage($role['task'], 'usestask');
  230. $fail = true;
  231. }
  232. }
  233. }
  234. }
  235. if ($fail) {
  236. return false;
  237. }
  238. $list = $this->_packageInfo['contents'];
  239. if (isset($list['dir']) && is_array($list['dir']) && isset($list['dir'][0])) {
  240. $this->_multipleToplevelDirNotAllowed();
  241. return $this->_isValid = 0;
  242. }
  243. $this->_validateFilelist();
  244. $this->_validateRelease();
  245. if (!$this->_stack->hasErrors()) {
  246. $chan = $this->_pf->_registry->getChannel($this->_pf->getChannel(), true);
  247. if (PEAR::isError($chan)) {
  248. $this->_unknownChannel($this->_pf->getChannel());
  249. } else {
  250. $valpack = $chan->getValidationPackage();
  251. // for channel validator packages, always use the default PEAR validator.
  252. // otherwise, they can't be installed or packaged
  253. $validator = $chan->getValidationObject($this->_pf->getPackage());
  254. if (!$validator) {
  255. $this->_stack->push(__FUNCTION__, 'error',
  256. array('channel' => $chan->getName(),
  257. 'package' => $this->_pf->getPackage(),
  258. 'name' => $valpack['_content'],
  259. 'version' => $valpack['attribs']['version']),
  260. 'package "%channel%/%package%" cannot be properly validated without ' .
  261. 'validation package "%channel%/%name%-%version%"');
  262. return $this->_isValid = 0;
  263. }
  264. $validator->setPackageFile($this->_pf);
  265. $validator->validate($state);
  266. $failures = $validator->getFailures();
  267. foreach ($failures['errors'] as $error) {
  268. $this->_stack->push(__FUNCTION__, 'error', $error,
  269. 'Channel validator error: field "%field%" - %reason%');
  270. }
  271. foreach ($failures['warnings'] as $warning) {
  272. $this->_stack->push(__FUNCTION__, 'warning', $warning,
  273. 'Channel validator warning: field "%field%" - %reason%');
  274. }
  275. }
  276. }
  277. $this->_pf->_isValid = $this->_isValid = !$this->_stack->hasErrors('error');
  278. if ($this->_isValid && $state == PEAR_VALIDATE_PACKAGING && !$this->_filesValid) {
  279. if ($this->_pf->getPackageType() == 'bundle') {
  280. if ($this->_analyzeBundledPackages()) {
  281. $this->_filesValid = $this->_pf->_filesValid = true;
  282. } else {
  283. $this->_pf->_isValid = $this->_isValid = 0;
  284. }
  285. } else {
  286. if (!$this->_analyzePhpFiles()) {
  287. $this->_pf->_isValid = $this->_isValid = 0;
  288. } else {
  289. $this->_filesValid = $this->_pf->_filesValid = true;
  290. }
  291. }
  292. }
  293. if ($this->_isValid) {
  294. return $this->_pf->_isValid = $this->_isValid = $state;
  295. }
  296. return $this->_pf->_isValid = $this->_isValid = 0;
  297. }
  298. function _stupidSchemaValidate($structure, $xml, $root)
  299. {
  300. if (!is_array($xml)) {
  301. $xml = array();
  302. }
  303. $keys = array_keys($xml);
  304. reset($keys);
  305. $key = current($keys);
  306. while ($key == 'attribs' || $key == '_contents') {
  307. $key = next($keys);
  308. }
  309. $unfoundtags = $optionaltags = array();
  310. $ret = true;
  311. $mismatch = false;
  312. foreach ($structure as $struc) {
  313. if ($key) {
  314. $tag = $xml[$key];
  315. }
  316. $test = $this->_processStructure($struc);
  317. if (isset($test['choices'])) {
  318. $loose = true;
  319. foreach ($test['choices'] as $choice) {
  320. if ($key == $choice['tag']) {
  321. $key = next($keys);
  322. while ($key == 'attribs' || $key == '_contents') {
  323. $key = next($keys);
  324. }
  325. $unfoundtags = $optionaltags = array();
  326. $mismatch = false;
  327. if ($key && $key != $choice['tag'] && isset($choice['multiple'])) {
  328. $unfoundtags[] = $choice['tag'];
  329. $optionaltags[] = $choice['tag'];
  330. if ($key) {
  331. $mismatch = true;
  332. }
  333. }
  334. $ret &= $this->_processAttribs($choice, $tag, $root);
  335. continue 2;
  336. } else {
  337. $unfoundtags[] = $choice['tag'];
  338. $mismatch = true;
  339. }
  340. if (!isset($choice['multiple']) || $choice['multiple'] != '*') {
  341. $loose = false;
  342. } else {
  343. $optionaltags[] = $choice['tag'];
  344. }
  345. }
  346. if (!$loose) {
  347. $this->_invalidTagOrder($unfoundtags, $key, $root);
  348. return false;
  349. }
  350. } else {
  351. if ($key != $test['tag']) {
  352. if (isset($test['multiple']) && $test['multiple'] != '*') {
  353. $unfoundtags[] = $test['tag'];
  354. $this->_invalidTagOrder($unfoundtags, $key, $root);
  355. return false;
  356. } else {
  357. if ($key) {
  358. $mismatch = true;
  359. }
  360. $unfoundtags[] = $test['tag'];
  361. $optionaltags[] = $test['tag'];
  362. }
  363. if (!isset($test['multiple'])) {
  364. $this->_invalidTagOrder($unfoundtags, $key, $root);
  365. return false;
  366. }
  367. continue;
  368. } else {
  369. $unfoundtags = $optionaltags = array();
  370. $mismatch = false;
  371. }
  372. $key = next($keys);
  373. while ($key == 'attribs' || $key == '_contents') {
  374. $key = next($keys);
  375. }
  376. if ($key && $key != $test['tag'] && isset($test['multiple'])) {
  377. $unfoundtags[] = $test['tag'];
  378. $optionaltags[] = $test['tag'];
  379. $mismatch = true;
  380. }
  381. $ret &= $this->_processAttribs($test, $tag, $root);
  382. continue;
  383. }
  384. }
  385. if (!$mismatch && count($optionaltags)) {
  386. // don't error out on any optional tags
  387. $unfoundtags = array_diff($unfoundtags, $optionaltags);
  388. }
  389. if (count($unfoundtags)) {
  390. $this->_invalidTagOrder($unfoundtags, $key, $root);
  391. } elseif ($key) {
  392. // unknown tags
  393. $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
  394. while ($key = next($keys)) {
  395. $this->_invalidTagOrder('*no tags allowed here*', $key, $root);
  396. }
  397. }
  398. return $ret;
  399. }
  400. function _processAttribs($choice, $tag, $context)
  401. {
  402. if (isset($choice['attribs'])) {
  403. if (!is_array($tag)) {
  404. $tag = array($tag);
  405. }
  406. $tags = $tag;
  407. if (!isset($tags[0])) {
  408. $tags = array($tags);
  409. }
  410. $ret = true;
  411. foreach ($tags as $i => $tag) {
  412. if (!is_array($tag) || !isset($tag['attribs'])) {
  413. foreach ($choice['attribs'] as $attrib) {
  414. if ($attrib{0} != '?') {
  415. $ret &= $this->_tagHasNoAttribs($choice['tag'],
  416. $context);
  417. continue 2;
  418. }
  419. }
  420. }
  421. foreach ($choice['attribs'] as $attrib) {
  422. if ($attrib{0} != '?') {
  423. if (!isset($tag['attribs'][$attrib])) {
  424. $ret &= $this->_tagMissingAttribute($choice['tag'],
  425. $attrib, $context);
  426. }
  427. }
  428. }
  429. }
  430. return $ret;
  431. }
  432. return true;
  433. }
  434. function _processStructure($key)
  435. {
  436. $ret = array();
  437. if (count($pieces = explode('|', $key)) > 1) {
  438. $ret['choices'] = array();
  439. foreach ($pieces as $piece) {
  440. $ret['choices'][] = $this->_processStructure($piece);
  441. }
  442. return $ret;
  443. }
  444. $multi = $key{0};
  445. if ($multi == '+' || $multi == '*') {
  446. $ret['multiple'] = $key{0};
  447. $key = substr($key, 1);
  448. }
  449. if (count($attrs = explode('->', $key)) > 1) {
  450. $ret['tag'] = array_shift($attrs);
  451. $ret['attribs'] = $attrs;
  452. } else {
  453. $ret['tag'] = $key;
  454. }
  455. return $ret;
  456. }
  457. function _validateStabilityVersion()
  458. {
  459. $structure = array('release', 'api');
  460. $a = $this->_stupidSchemaValidate($structure, $this->_packageInfo['version'], '<version>');
  461. $a &= $this->_stupidSchemaValidate($structure, $this->_packageInfo['stability'], '<stability>');
  462. if ($a) {
  463. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  464. $this->_packageInfo['version']['release'])) {
  465. $this->_invalidVersion('release', $this->_packageInfo['version']['release']);
  466. }
  467. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  468. $this->_packageInfo['version']['api'])) {
  469. $this->_invalidVersion('api', $this->_packageInfo['version']['api']);
  470. }
  471. if (!in_array($this->_packageInfo['stability']['release'],
  472. array('snapshot', 'devel', 'alpha', 'beta', 'stable'))) {
  473. $this->_invalidState('release', $this->_packageInfo['stability']['release']);
  474. }
  475. if (!in_array($this->_packageInfo['stability']['api'],
  476. array('devel', 'alpha', 'beta', 'stable'))) {
  477. $this->_invalidState('api', $this->_packageInfo['stability']['api']);
  478. }
  479. }
  480. }
  481. function _validateMaintainers()
  482. {
  483. $structure =
  484. array(
  485. 'name',
  486. 'user',
  487. 'email',
  488. 'active',
  489. );
  490. foreach (array('lead', 'developer', 'contributor', 'helper') as $type) {
  491. if (!isset($this->_packageInfo[$type])) {
  492. continue;
  493. }
  494. if (isset($this->_packageInfo[$type][0])) {
  495. foreach ($this->_packageInfo[$type] as $lead) {
  496. $this->_stupidSchemaValidate($structure, $lead, '<' . $type . '>');
  497. }
  498. } else {
  499. $this->_stupidSchemaValidate($structure, $this->_packageInfo[$type],
  500. '<' . $type . '>');
  501. }
  502. }
  503. }
  504. function _validatePhpDep($dep, $installcondition = false)
  505. {
  506. $structure = array(
  507. 'min',
  508. '*max',
  509. '*exclude',
  510. );
  511. $type = $installcondition ? '<installcondition><php>' : '<dependencies><required><php>';
  512. $this->_stupidSchemaValidate($structure, $dep, $type);
  513. if (isset($dep['min'])) {
  514. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
  515. $dep['min'])) {
  516. $this->_invalidVersion($type . '<min>', $dep['min']);
  517. }
  518. }
  519. if (isset($dep['max'])) {
  520. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
  521. $dep['max'])) {
  522. $this->_invalidVersion($type . '<max>', $dep['max']);
  523. }
  524. }
  525. if (isset($dep['exclude'])) {
  526. if (!is_array($dep['exclude'])) {
  527. $dep['exclude'] = array($dep['exclude']);
  528. }
  529. foreach ($dep['exclude'] as $exclude) {
  530. if (!preg_match(
  531. '/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?(?:-[a-zA-Z0-9]+)?\\z/',
  532. $exclude)) {
  533. $this->_invalidVersion($type . '<exclude>', $exclude);
  534. }
  535. }
  536. }
  537. }
  538. function _validatePearinstallerDep($dep)
  539. {
  540. $structure = array(
  541. 'min',
  542. '*max',
  543. '*recommended',
  544. '*exclude',
  545. );
  546. $this->_stupidSchemaValidate($structure, $dep, '<dependencies><required><pearinstaller>');
  547. if (isset($dep['min'])) {
  548. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  549. $dep['min'])) {
  550. $this->_invalidVersion('<dependencies><required><pearinstaller><min>',
  551. $dep['min']);
  552. }
  553. }
  554. if (isset($dep['max'])) {
  555. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  556. $dep['max'])) {
  557. $this->_invalidVersion('<dependencies><required><pearinstaller><max>',
  558. $dep['max']);
  559. }
  560. }
  561. if (isset($dep['recommended'])) {
  562. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  563. $dep['recommended'])) {
  564. $this->_invalidVersion('<dependencies><required><pearinstaller><recommended>',
  565. $dep['recommended']);
  566. }
  567. }
  568. if (isset($dep['exclude'])) {
  569. if (!is_array($dep['exclude'])) {
  570. $dep['exclude'] = array($dep['exclude']);
  571. }
  572. foreach ($dep['exclude'] as $exclude) {
  573. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  574. $exclude)) {
  575. $this->_invalidVersion('<dependencies><required><pearinstaller><exclude>',
  576. $exclude);
  577. }
  578. }
  579. }
  580. }
  581. function _validatePackageDep($dep, $group, $type = '<package>')
  582. {
  583. if (isset($dep['uri'])) {
  584. if (isset($dep['conflicts'])) {
  585. $structure = array(
  586. 'name',
  587. 'uri',
  588. 'conflicts',
  589. '*providesextension',
  590. );
  591. } else {
  592. $structure = array(
  593. 'name',
  594. 'uri',
  595. '*providesextension',
  596. );
  597. }
  598. } else {
  599. if (isset($dep['conflicts'])) {
  600. $structure = array(
  601. 'name',
  602. 'channel',
  603. '*min',
  604. '*max',
  605. '*exclude',
  606. 'conflicts',
  607. '*providesextension',
  608. );
  609. } else {
  610. $structure = array(
  611. 'name',
  612. 'channel',
  613. '*min',
  614. '*max',
  615. '*recommended',
  616. '*exclude',
  617. '*nodefault',
  618. '*providesextension',
  619. );
  620. }
  621. }
  622. if (isset($dep['name'])) {
  623. $type .= '<name>' . $dep['name'] . '</name>';
  624. }
  625. $this->_stupidSchemaValidate($structure, $dep, '<dependencies>' . $group . $type);
  626. if (isset($dep['uri']) && (isset($dep['min']) || isset($dep['max']) ||
  627. isset($dep['recommended']) || isset($dep['exclude']))) {
  628. $this->_uriDepsCannotHaveVersioning('<dependencies>' . $group . $type);
  629. }
  630. if (isset($dep['channel']) && strtolower($dep['channel']) == '__uri') {
  631. $this->_DepchannelCannotBeUri('<dependencies>' . $group . $type);
  632. }
  633. if (isset($dep['min'])) {
  634. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  635. $dep['min'])) {
  636. $this->_invalidVersion('<dependencies>' . $group . $type . '<min>', $dep['min']);
  637. }
  638. }
  639. if (isset($dep['max'])) {
  640. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  641. $dep['max'])) {
  642. $this->_invalidVersion('<dependencies>' . $group . $type . '<max>', $dep['max']);
  643. }
  644. }
  645. if (isset($dep['recommended'])) {
  646. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  647. $dep['recommended'])) {
  648. $this->_invalidVersion('<dependencies>' . $group . $type . '<recommended>',
  649. $dep['recommended']);
  650. }
  651. }
  652. if (isset($dep['exclude'])) {
  653. if (!is_array($dep['exclude'])) {
  654. $dep['exclude'] = array($dep['exclude']);
  655. }
  656. foreach ($dep['exclude'] as $exclude) {
  657. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  658. $exclude)) {
  659. $this->_invalidVersion('<dependencies>' . $group . $type . '<exclude>',
  660. $exclude);
  661. }
  662. }
  663. }
  664. }
  665. function _validateSubpackageDep($dep, $group)
  666. {
  667. $this->_validatePackageDep($dep, $group, '<subpackage>');
  668. if (isset($dep['providesextension'])) {
  669. $this->_subpackageCannotProvideExtension(isset($dep['name']) ? $dep['name'] : '');
  670. }
  671. if (isset($dep['conflicts'])) {
  672. $this->_subpackagesCannotConflict(isset($dep['name']) ? $dep['name'] : '');
  673. }
  674. }
  675. function _validateExtensionDep($dep, $group = false, $installcondition = false)
  676. {
  677. if (isset($dep['conflicts'])) {
  678. $structure = array(
  679. 'name',
  680. '*min',
  681. '*max',
  682. '*exclude',
  683. 'conflicts',
  684. );
  685. } else {
  686. $structure = array(
  687. 'name',
  688. '*min',
  689. '*max',
  690. '*recommended',
  691. '*exclude',
  692. );
  693. }
  694. if ($installcondition) {
  695. $type = '<installcondition><extension>';
  696. } else {
  697. $type = '<dependencies>' . $group . '<extension>';
  698. }
  699. if (isset($dep['name'])) {
  700. $type .= '<name>' . $dep['name'] . '</name>';
  701. }
  702. $this->_stupidSchemaValidate($structure, $dep, $type);
  703. if (isset($dep['min'])) {
  704. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  705. $dep['min'])) {
  706. $this->_invalidVersion(substr($type, 1) . '<min', $dep['min']);
  707. }
  708. }
  709. if (isset($dep['max'])) {
  710. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  711. $dep['max'])) {
  712. $this->_invalidVersion(substr($type, 1) . '<max', $dep['max']);
  713. }
  714. }
  715. if (isset($dep['recommended'])) {
  716. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  717. $dep['recommended'])) {
  718. $this->_invalidVersion(substr($type, 1) . '<recommended', $dep['recommended']);
  719. }
  720. }
  721. if (isset($dep['exclude'])) {
  722. if (!is_array($dep['exclude'])) {
  723. $dep['exclude'] = array($dep['exclude']);
  724. }
  725. foreach ($dep['exclude'] as $exclude) {
  726. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  727. $exclude)) {
  728. $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
  729. }
  730. }
  731. }
  732. }
  733. function _validateOsDep($dep, $installcondition = false)
  734. {
  735. $structure = array(
  736. 'name',
  737. '*conflicts',
  738. );
  739. $type = $installcondition ? '<installcondition><os>' : '<dependencies><required><os>';
  740. if ($this->_stupidSchemaValidate($structure, $dep, $type)) {
  741. if ($dep['name'] == '*') {
  742. if (array_key_exists('conflicts', $dep)) {
  743. $this->_cannotConflictWithAllOs($type);
  744. }
  745. }
  746. }
  747. }
  748. function _validateArchDep($dep, $installcondition = false)
  749. {
  750. $structure = array(
  751. 'pattern',
  752. '*conflicts',
  753. );
  754. $type = $installcondition ? '<installcondition><arch>' : '<dependencies><required><arch>';
  755. $this->_stupidSchemaValidate($structure, $dep, $type);
  756. }
  757. function _validateInstallConditions($cond, $release)
  758. {
  759. $structure = array(
  760. '*php',
  761. '*extension',
  762. '*os',
  763. '*arch',
  764. );
  765. if (!$this->_stupidSchemaValidate($structure,
  766. $cond, $release)) {
  767. return false;
  768. }
  769. foreach (array('php', 'extension', 'os', 'arch') as $type) {
  770. if (isset($cond[$type])) {
  771. $iter = $cond[$type];
  772. if (!is_array($iter) || !isset($iter[0])) {
  773. $iter = array($iter);
  774. }
  775. foreach ($iter as $package) {
  776. if ($type == 'extension') {
  777. $this->{"_validate{$type}Dep"}($package, false, true);
  778. } else {
  779. $this->{"_validate{$type}Dep"}($package, true);
  780. }
  781. }
  782. }
  783. }
  784. }
  785. function _validateDependencies()
  786. {
  787. $structure = array(
  788. 'required',
  789. '*optional',
  790. '*group->name->hint'
  791. );
  792. if (!$this->_stupidSchemaValidate($structure,
  793. $this->_packageInfo['dependencies'], '<dependencies>')) {
  794. return false;
  795. }
  796. foreach (array('required', 'optional') as $simpledep) {
  797. if (isset($this->_packageInfo['dependencies'][$simpledep])) {
  798. if ($simpledep == 'optional') {
  799. $structure = array(
  800. '*package',
  801. '*subpackage',
  802. '*extension',
  803. );
  804. } else {
  805. $structure = array(
  806. 'php',
  807. 'pearinstaller',
  808. '*package',
  809. '*subpackage',
  810. '*extension',
  811. '*os',
  812. '*arch',
  813. );
  814. }
  815. if ($this->_stupidSchemaValidate($structure,
  816. $this->_packageInfo['dependencies'][$simpledep],
  817. "<dependencies><$simpledep>")) {
  818. foreach (array('package', 'subpackage', 'extension') as $type) {
  819. if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
  820. $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
  821. if (!isset($iter[0])) {
  822. $iter = array($iter);
  823. }
  824. foreach ($iter as $package) {
  825. if ($type != 'extension') {
  826. if (isset($package['uri'])) {
  827. if (isset($package['channel'])) {
  828. $this->_UrlOrChannel($type,
  829. $package['name']);
  830. }
  831. } else {
  832. if (!isset($package['channel'])) {
  833. $this->_NoChannel($type, $package['name']);
  834. }
  835. }
  836. }
  837. $this->{"_validate{$type}Dep"}($package, "<$simpledep>");
  838. }
  839. }
  840. }
  841. if ($simpledep == 'optional') {
  842. continue;
  843. }
  844. foreach (array('php', 'pearinstaller', 'os', 'arch') as $type) {
  845. if (isset($this->_packageInfo['dependencies'][$simpledep][$type])) {
  846. $iter = $this->_packageInfo['dependencies'][$simpledep][$type];
  847. if (!isset($iter[0])) {
  848. $iter = array($iter);
  849. }
  850. foreach ($iter as $package) {
  851. $this->{"_validate{$type}Dep"}($package);
  852. }
  853. }
  854. }
  855. }
  856. }
  857. }
  858. if (isset($this->_packageInfo['dependencies']['group'])) {
  859. $groups = $this->_packageInfo['dependencies']['group'];
  860. if (!isset($groups[0])) {
  861. $groups = array($groups);
  862. }
  863. $structure = array(
  864. '*package',
  865. '*subpackage',
  866. '*extension',
  867. );
  868. foreach ($groups as $group) {
  869. if ($this->_stupidSchemaValidate($structure, $group, '<group>')) {
  870. if (!PEAR_Validate::validGroupName($group['attribs']['name'])) {
  871. $this->_invalidDepGroupName($group['attribs']['name']);
  872. }
  873. foreach (array('package', 'subpackage', 'extension') as $type) {
  874. if (isset($group[$type])) {
  875. $iter = $group[$type];
  876. if (!isset($iter[0])) {
  877. $iter = array($iter);
  878. }
  879. foreach ($iter as $package) {
  880. if ($type != 'extension') {
  881. if (isset($package['uri'])) {
  882. if (isset($package['channel'])) {
  883. $this->_UrlOrChannelGroup($type,
  884. $package['name'],
  885. $group['name']);
  886. }
  887. } else {
  888. if (!isset($package['channel'])) {
  889. $this->_NoChannelGroup($type,
  890. $package['name'],
  891. $group['name']);
  892. }
  893. }
  894. }
  895. $this->{"_validate{$type}Dep"}($package, '<group name="' .
  896. $group['attribs']['name'] . '">');
  897. }
  898. }
  899. }
  900. }
  901. }
  902. }
  903. }
  904. function _validateCompatible()
  905. {
  906. $compat = $this->_packageInfo['compatible'];
  907. if (!isset($compat[0])) {
  908. $compat = array($compat);
  909. }
  910. $required = array('name', 'channel', 'min', 'max', '*exclude');
  911. foreach ($compat as $package) {
  912. $type = '<compatible>';
  913. if (is_array($package) && array_key_exists('name', $package)) {
  914. $type .= '<name>' . $package['name'] . '</name>';
  915. }
  916. $this->_stupidSchemaValidate($required, $package, $type);
  917. if (is_array($package) && array_key_exists('min', $package)) {
  918. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  919. $package['min'])) {
  920. $this->_invalidVersion(substr($type, 1) . '<min', $package['min']);
  921. }
  922. }
  923. if (is_array($package) && array_key_exists('max', $package)) {
  924. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  925. $package['max'])) {
  926. $this->_invalidVersion(substr($type, 1) . '<max', $package['max']);
  927. }
  928. }
  929. if (is_array($package) && array_key_exists('exclude', $package)) {
  930. if (!is_array($package['exclude'])) {
  931. $package['exclude'] = array($package['exclude']);
  932. }
  933. foreach ($package['exclude'] as $exclude) {
  934. if (!preg_match('/^\d+(?:\.\d+)*(?:[a-zA-Z]+\d*)?\\z/',
  935. $exclude)) {
  936. $this->_invalidVersion(substr($type, 1) . '<exclude', $exclude);
  937. }
  938. }
  939. }
  940. }
  941. }
  942. function _validateBundle($list)
  943. {
  944. if (!is_array($list) || !isset($list['bundledpackage'])) {
  945. return $this->_NoBundledPackages();
  946. }
  947. if (!is_array($list['bundledpackage']) || !isset($list['bundledpackage'][0])) {
  948. return $this->_AtLeast2BundledPackages();
  949. }
  950. foreach ($list['bundledpackage'] as $package) {
  951. if (!is_string($package)) {
  952. $this->_bundledPackagesMustBeFilename();
  953. }
  954. }
  955. }
  956. function _validateFilelist($list = false, $allowignore = false, $dirs = '')
  957. {
  958. $iscontents = false;
  959. if (!$list) {
  960. $iscontents = true;
  961. $list = $this->_packageInfo['contents'];
  962. if (isset($this->_packageInfo['bundle'])) {
  963. return $this->_validateBundle($list);
  964. }
  965. }
  966. if ($allowignore) {
  967. $struc = array(
  968. '*install->name->as',
  969. '*ignore->name'
  970. );
  971. } else {
  972. $struc = array(
  973. '*dir->name->?baseinstalldir',
  974. '*file->name->role->?baseinstalldir->?md5sum'
  975. );
  976. if (isset($list['dir']) && isset($list['file'])) {
  977. // stave off validation errors without requiring a set order.
  978. $_old = $list;
  979. if (isset($list['attribs'])) {
  980. $list = array('attribs' => $_old['attribs']);
  981. }
  982. $list['dir'] = $_old['dir'];
  983. $list['file'] = $_old['file'];
  984. }
  985. }
  986. if (!isset($list['attribs']) || !isset($list['attribs']['name'])) {
  987. $unknown = $allowignore ? '<filelist>' : '<dir name="*unknown*">';
  988. $dirname = $iscontents ? '<contents>' : $unknown;
  989. } else {
  990. $dirname = '<dir name="' . $list['attribs']['name'] . '">';
  991. if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
  992. str_replace('\\', '/', $list['attribs']['name']))) {
  993. // file contains .. parent directory or . cur directory
  994. $this->_invalidDirName($list['attribs']['name']);
  995. }
  996. }
  997. $res = $this->_stupidSchemaValidate($struc, $list, $dirname);
  998. if ($allowignore && $res) {
  999. $ignored_or_installed = array();
  1000. $this->_pf->getFilelist();
  1001. $fcontents = $this->_pf->getContents();
  1002. $filelist = array();
  1003. if (!isset($fcontents['dir']['file'][0])) {
  1004. $fcontents['dir']['file'] = array($fcontents['dir']['file']);
  1005. }
  1006. foreach ($fcontents['dir']['file'] as $file) {
  1007. $filelist[$file['attribs']['name']] = true;
  1008. }
  1009. if (isset($list['install'])) {
  1010. if (!isset($list['install'][0])) {
  1011. $list['install'] = array($list['install']);
  1012. }
  1013. foreach ($list['install'] as $file) {
  1014. if (!isset($filelist[$file['attribs']['name']])) {
  1015. $this->_notInContents($file['attribs']['name'], 'install');
  1016. continue;
  1017. }
  1018. if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
  1019. $this->_multipleInstallAs($file['attribs']['name']);
  1020. }
  1021. if (!isset($ignored_or_installed[$file['attribs']['name']])) {
  1022. $ignored_or_installed[$file['attribs']['name']] = array();
  1023. }
  1024. $ignored_or_installed[$file['attribs']['name']][] = 1;
  1025. if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
  1026. str_replace('\\', '/', $file['attribs']['as']))) {
  1027. // file contains .. parent directory or . cur directory references
  1028. $this->_invalidFileInstallAs($file['attribs']['name'],
  1029. $file['attribs']['as']);
  1030. }
  1031. }
  1032. }
  1033. if (isset($list['ignore'])) {
  1034. if (!isset($list['ignore'][0])) {
  1035. $list['ignore'] = array($list['ignore']);
  1036. }
  1037. foreach ($list['ignore'] as $file) {
  1038. if (!isset($filelist[$file['attribs']['name']])) {
  1039. $this->_notInContents($file['attribs']['name'], 'ignore');
  1040. continue;
  1041. }
  1042. if (array_key_exists($file['attribs']['name'], $ignored_or_installed)) {
  1043. $this->_ignoreAndInstallAs($file['attribs']['name']);
  1044. }
  1045. }
  1046. }
  1047. }
  1048. if (!$allowignore && isset($list['file'])) {
  1049. if (is_string($list['file'])) {
  1050. $this->_oldStyleFileNotAllowed();
  1051. return false;
  1052. }
  1053. if (!isset($list['file'][0])) {
  1054. // single file
  1055. $list['file'] = array($list['file']);
  1056. }
  1057. foreach ($list['file'] as $i => $file)
  1058. {
  1059. if (isset($file['attribs']) && isset($file['attribs']['name'])) {
  1060. if ($file['attribs']['name']{0} == '.' &&
  1061. $file['attribs']['name']{1} == '/') {
  1062. // name is something like "./doc/whatever.txt"
  1063. $this->_invalidFileName($file['attribs']['name'], $dirname);
  1064. }
  1065. if (preg_match('~/\.\.?(/|\\z)|^\.\.?/~',
  1066. str_replace('\\', '/', $file['attribs']['name']))) {
  1067. // file contains .. parent directory or . cur directory
  1068. $this->_invalidFileName($file['attribs']['name'], $dirname);
  1069. }
  1070. }
  1071. if (isset($file['attribs']) && isset($file['attribs']['role'])) {
  1072. if (!$this->_validateRole($file['attribs']['role'])) {
  1073. if (isset($this->_packageInfo['usesrole'])) {
  1074. $roles = $this->_packageInfo['usesrole'];
  1075. if (!isset($roles[0])) {
  1076. $roles = array($roles);
  1077. }
  1078. foreach ($roles as $role) {
  1079. if ($role['role'] = $file['attribs']['role']) {
  1080. $msg = 'This package contains role "%role%" and requires ' .
  1081. 'package "%package%" to be used';
  1082. if (isset($role['uri'])) {
  1083. $params = array('role' => $role['role'],
  1084. 'package' => $role['uri']);
  1085. } else {
  1086. $params = array('role' => $role['role'],
  1087. 'package' => $this->_pf->_registry->
  1088. parsedPackageNameToString(array('package' =>
  1089. $role['package'], 'channel' => $role['channel']),
  1090. true));
  1091. }
  1092. $this->_stack->push('_mustInstallRole', 'error', $params, $msg);
  1093. }
  1094. }
  1095. }
  1096. $this->_invalidFileRole($file['attribs']['name'],
  1097. $dirname, $file['attribs']['role']);
  1098. }
  1099. }
  1100. if (!isset($file['attribs'])) {
  1101. continue;
  1102. }
  1103. $save = $file['attribs'];
  1104. if ($dirs) {
  1105. $save['name'] = $dirs . '/' . $save['name'];
  1106. }
  1107. unset($file['attribs']);
  1108. if (count($file) && $this->_curState != PEAR_VALIDATE_DOWNLOADING) { // has tasks
  1109. foreach ($file as $task => $value) {
  1110. if ($tagClass = $this->_pf->getTask($task)) {
  1111. if (!is_array($value) || !isset($value[0])) {
  1112. $value = array($value);
  1113. }
  1114. foreach ($value as $v) {
  1115. $ret = call_user_func(array($tagClass, 'validateXml'),
  1116. $this->_pf, $v, $this->_pf->_config, $save);
  1117. if (is_array($ret)) {
  1118. $this->_invalidTask($task, $ret, isset($save['name']) ?
  1119. $save['name'] : '');
  1120. }
  1121. }
  1122. } else {
  1123. if (isset($this->_packageInfo['usestask'])) {
  1124. $roles = $this->_packageInfo['usestask'];
  1125. if (!isset($roles[0])) {
  1126. $roles = array($roles);
  1127. }
  1128. foreach ($roles as $role) {
  1129. if ($role['task'] = $task) {
  1130. $msg = 'This package contains task "%task%" and requires ' .
  1131. 'package "%package%" to be used';
  1132. if (isset($role['uri'])) {
  1133. $params = array('task' => $role['task'],
  1134. 'package' => $role['uri']);
  1135. } else {
  1136. $params = array('task' => $role['task'],
  1137. 'package' => $this->_pf->_registry->
  1138. parsedPackageNameToString(array('package' =>
  1139. $role['package'], 'channel' => $role['channel']),
  1140. true));
  1141. }
  1142. $this->_stack->push('_mustInstallTask', 'error',
  1143. $params, $msg);
  1144. }
  1145. }
  1146. }
  1147. $this->_unknownTask($task, $save['name']);
  1148. }
  1149. }
  1150. }
  1151. }
  1152. }
  1153. if (isset($list['ignore'])) {
  1154. if (!$allowignore) {
  1155. $this->_ignoreNotAllowed('ignore');
  1156. }
  1157. }
  1158. if (isset($list['install'])) {
  1159. if (!$allowignore) {
  1160. $this->_ignoreNotAllowed('install');
  1161. }
  1162. }
  1163. if (isset($list['file'])) {
  1164. if ($allowignore) {
  1165. $this->_fileNotAllowed('file');
  1166. }
  1167. }
  1168. if (isset($list['dir'])) {
  1169. if ($allowignore) {
  1170. $this->_fileNotAllowed('dir');
  1171. } else {
  1172. if (!isset($list['dir'][0])) {
  1173. $list['dir'] = array($list['dir']);
  1174. }
  1175. foreach ($list['dir'] as $dir) {
  1176. if (isset($dir['attribs']) && isset($dir['attribs']['name'])) {
  1177. if ($dir['attribs']['name'] == '/' ||
  1178. !isset($this->_packageInfo['contents']['dir']['dir'])) {
  1179. // always use nothing if the filelist has already been flattened
  1180. $newdirs = '';
  1181. } elseif ($dirs == '') {
  1182. $newdirs = $dir['attribs']['name'];
  1183. } else {
  1184. $newdirs = $dirs . '/' . $dir['attribs']['name'];
  1185. }
  1186. } else {
  1187. $newdirs = $dirs;
  1188. }
  1189. $this->_validateFilelist($dir, $allowignore, $newdirs);
  1190. }
  1191. }
  1192. }
  1193. }
  1194. function _validateRelease()
  1195. {
  1196. if (isset($this->_packageInfo['phprelease'])) {
  1197. $release = 'phprelease';
  1198. if (isset($this->_packageInfo['providesextension'])) {
  1199. $this->_cannotProvideExtension($release);
  1200. }
  1201. if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
  1202. $this->_cannotHaveSrcpackage($release);
  1203. }
  1204. $releases = $this->_packageInfo['phprelease'];
  1205. if (!is_array($releases)) {
  1206. return true;
  1207. }
  1208. if (!isset($releases[0])) {
  1209. $releases = array($releases);
  1210. }
  1211. foreach ($releases as $rel) {
  1212. $this->_stupidSchemaValidate(array(
  1213. '*installconditions',
  1214. '*filelist',
  1215. ), $rel, '<phprelease>');
  1216. }
  1217. }
  1218. foreach (array('', 'zend') as $prefix) {
  1219. $releasetype = $prefix . 'extsrcrelease';
  1220. if (isset($this->_packageInfo[$releasetype])) {
  1221. $release = $releasetype;
  1222. if (!isset($this->_packageInfo['providesextension'])) {
  1223. $this->_mustProvideExtension($release);
  1224. }
  1225. if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
  1226. $this->_cannotHaveSrcpackage($release);
  1227. }
  1228. $releases = $this->_packageInfo[$releasetype];
  1229. if (!is_array($releases)) {
  1230. return true;
  1231. }
  1232. if (!isset($releases[0])) {
  1233. $releases = array($releases);
  1234. }
  1235. foreach ($releases as $rel) {
  1236. $this->_stupidSchemaValidate(array(
  1237. '*installconditions',
  1238. '*configureoption->name->prompt->?default',
  1239. '*binarypackage',
  1240. '*filelist',
  1241. ), $rel, '<' . $releasetype . '>');
  1242. if (isset($rel['binarypackage'])) {
  1243. if (!is_array($rel['binarypackage']) || !isset($rel['binarypackage'][0])) {
  1244. $rel['binarypackage'] = array($rel['binarypackage']);
  1245. }
  1246. foreach ($rel['binarypackage'] as $bin) {
  1247. if (!is_string($bin)) {
  1248. $this->_binaryPackageMustBePackagename();
  1249. }
  1250. }
  1251. }
  1252. }
  1253. }
  1254. $releasetype = 'extbinrelease';
  1255. if (isset($this->_packageInfo[$releasetype])) {
  1256. $release = $releasetype;
  1257. if (!isset($this->_packageInfo['providesextension'])) {
  1258. $this->_mustProvideExtension($release);
  1259. }
  1260. if (isset($this->_packageInfo['channel']) &&
  1261. !isset($this->_packageInfo['srcpackage'])) {
  1262. $this->_mustSrcPackage($release);
  1263. }
  1264. if (isset($this->_packageInfo['uri']) && !isset($this->_packageInfo['srcuri'])) {
  1265. $this->_mustSrcuri($release);
  1266. }
  1267. $releases = $this->_packageInfo[$releasetype];
  1268. if (!is_array($releases)) {
  1269. return true;
  1270. }
  1271. if (!isset($releases[0])) {
  1272. $releases = array($releases);
  1273. }
  1274. foreach ($releases as $rel) {
  1275. $this->_stupidSchemaValidate(array(
  1276. '*installconditions',
  1277. '*filelist',
  1278. ), $rel, '<' . $releasetype . '>');
  1279. }
  1280. }
  1281. }
  1282. if (isset($this->_packageInfo['bundle'])) {
  1283. $release = 'bundle';
  1284. if (isset($this->_packageInfo['providesextension'])) {
  1285. $this->_cannotProvideExtension($release);
  1286. }
  1287. if (isset($this->_packageInfo['srcpackage']) || isset($this->_packageInfo['srcuri'])) {
  1288. $this->_cannotHaveSrcpackage($release);
  1289. }
  1290. $releases = $this->_packageInfo['bundle'];
  1291. if (!is_array($releases) || !isset($releases[0])) {
  1292. $releases = array($releases);
  1293. }
  1294. foreach ($releases as $rel) {
  1295. $this->_stupidSchemaValidate(array(
  1296. '*installconditions',
  1297. '*filelist',
  1298. ), $rel, '<bundle>');
  1299. }
  1300. }
  1301. foreach ($releases as $rel) {
  1302. if (is_array($rel) && array_key_exists('installconditions', $rel)) {
  1303. $this->_validateInstallConditions($rel['installconditions'],
  1304. "<$release><installconditions>");
  1305. }
  1306. if (is_array($rel) && array_key_exists('filelist', $rel)) {
  1307. if ($rel['filelist']) {
  1308. $this->_validateFilelist($rel['filelist'], true);
  1309. }
  1310. }
  1311. }
  1312. }
  1313. /**
  1314. * This is here to allow role extension through plugins
  1315. * @param string
  1316. */
  1317. function _validateRole($role)
  1318. {
  1319. return in_array($role, PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType()));
  1320. }
  1321. function _pearVersionTooLow($version)
  1322. {
  1323. $this->_stack->push(__FUNCTION__, 'error',
  1324. array('version' => $version),
  1325. 'This package.xml requires PEAR version %version% to parse properly, we are ' .
  1326. 'version 1.9.4');
  1327. }
  1328. function _invalidTagOrder($oktags, $actual, $root)
  1329. {
  1330. $this->_stack->push(__FUNCTION__, 'error',
  1331. array('oktags' => $oktags, 'actual' => $actual, 'root' => $root),
  1332. 'Invalid tag order in %root%, found <%actual%> expected one of "%oktags%"');
  1333. }
  1334. function _ignoreNotAllowed($type)
  1335. {
  1336. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
  1337. '<%type%> is not allowed inside global <contents>, only inside ' .
  1338. '<phprelease>/<extbinrelease>/<zendextbinrelease>, use <dir> and <file> only');
  1339. }
  1340. function _fileNotAllowed($type)
  1341. {
  1342. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
  1343. '<%type%> is not allowed inside release <filelist>, only inside ' .
  1344. '<contents>, use <ignore> and <install> only');
  1345. }
  1346. function _oldStyleFileNotAllowed()
  1347. {
  1348. $this->_stack->push(__FUNCTION__, 'error', array(),
  1349. 'Old-style <file>name</file> is not allowed. Use' .
  1350. '<file name="name" role="role"/>');
  1351. }
  1352. function _tagMissingAttribute($tag, $attr, $context)
  1353. {
  1354. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
  1355. 'attribute' => $attr, 'context' => $context),
  1356. 'tag <%tag%> in context "%context%" has no attribute "%attribute%"');
  1357. }
  1358. function _tagHasNoAttribs($tag, $context)
  1359. {
  1360. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag,
  1361. 'context' => $context),
  1362. 'tag <%tag%> has no attributes in context "%context%"');
  1363. }
  1364. function _invalidInternalStructure()
  1365. {
  1366. $this->_stack->push(__FUNCTION__, 'exception', array(),
  1367. 'internal array was not generated by compatible parser, or extreme parser error, cannot continue');
  1368. }
  1369. function _invalidFileRole($file, $dir, $role)
  1370. {
  1371. $this->_stack->push(__FUNCTION__, 'error', array(
  1372. 'file' => $file, 'dir' => $dir, 'role' => $role,
  1373. 'roles' => PEAR_Installer_Role::getValidRoles($this->_pf->getPackageType())),
  1374. 'File "%file%" in directory "%dir%" has invalid role "%role%", should be one of %roles%');
  1375. }
  1376. function _invalidFileName($file, $dir)
  1377. {
  1378. $this->_stack->push(__FUNCTION__, 'error', array(
  1379. 'file' => $file),
  1380. 'File "%file%" in directory "%dir%" cannot begin with "./" or contain ".."');
  1381. }
  1382. function _invalidFileInstallAs($file, $as)
  1383. {
  1384. $this->_stack->push(__FUNCTION__, 'error', array(
  1385. 'file' => $file, 'as' => $as),
  1386. 'File "%file%" <install as="%as%"/> cannot contain "./" or contain ".."');
  1387. }
  1388. function _invalidDirName($dir)
  1389. {
  1390. $this->_stack->push(__FUNCTION__, 'error', array(
  1391. 'dir' => $file),
  1392. 'Directory "%dir%" cannot begin with "./" or contain ".."');
  1393. }
  1394. function _filelistCannotContainFile($filelist)
  1395. {
  1396. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
  1397. '<%tag%> can only contain <dir>, contains <file>. Use ' .
  1398. '<dir name="/"> as the first dir element');
  1399. }
  1400. function _filelistMustContainDir($filelist)
  1401. {
  1402. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $filelist),
  1403. '<%tag%> must contain <dir>. Use <dir name="/"> as the ' .
  1404. 'first dir element');
  1405. }
  1406. function _tagCannotBeEmpty($tag)
  1407. {
  1408. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
  1409. '<%tag%> cannot be empty (<%tag%/>)');
  1410. }
  1411. function _UrlOrChannel($type, $name)
  1412. {
  1413. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
  1414. 'name' => $name),
  1415. 'Required dependency <%type%> "%name%" can have either url OR ' .
  1416. 'channel attributes, and not both');
  1417. }
  1418. function _NoChannel($type, $name)
  1419. {
  1420. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
  1421. 'name' => $name),
  1422. 'Required dependency <%type%> "%name%" must have either url OR ' .
  1423. 'channel attributes');
  1424. }
  1425. function _UrlOrChannelGroup($type, $name, $group)
  1426. {
  1427. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
  1428. 'name' => $name, 'group' => $group),
  1429. 'Group "%group%" dependency <%type%> "%name%" can have either url OR ' .
  1430. 'channel attributes, and not both');
  1431. }
  1432. function _NoChannelGroup($type, $name, $group)
  1433. {
  1434. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type,
  1435. 'name' => $name, 'group' => $group),
  1436. 'Group "%group%" dependency <%type%> "%name%" must have either url OR ' .
  1437. 'channel attributes');
  1438. }
  1439. function _unknownChannel($channel)
  1440. {
  1441. $this->_stack->push(__FUNCTION__, 'error', array('channel' => $channel),
  1442. 'Unknown channel "%channel%"');
  1443. }
  1444. function _noPackageVersion()
  1445. {
  1446. $this->_stack->push(__FUNCTION__, 'error', array(),
  1447. 'package.xml <package> tag has no version attribute, or version is not 2.0');
  1448. }
  1449. function _NoBundledPackages()
  1450. {
  1451. $this->_stack->push(__FUNCTION__, 'error', array(),
  1452. 'No <bundledpackage> tag was found in <contents>, required for bundle packages');
  1453. }
  1454. function _AtLeast2BundledPackages()
  1455. {
  1456. $this->_stack->push(__FUNCTION__, 'error', array(),
  1457. 'At least 2 packages must be bundled in a bundle package');
  1458. }
  1459. function _ChannelOrUri($name)
  1460. {
  1461. $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
  1462. 'Bundled package "%name%" can have either a uri or a channel, not both');
  1463. }
  1464. function _noChildTag($child, $tag)
  1465. {
  1466. $this->_stack->push(__FUNCTION__, 'error', array('child' => $child, 'tag' => $tag),
  1467. 'Tag <%tag%> is missing child tag <%child%>');
  1468. }
  1469. function _invalidVersion($type, $value)
  1470. {
  1471. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value),
  1472. 'Version type <%type%> is not a valid version (%value%)');
  1473. }
  1474. function _invalidState($type, $value)
  1475. {
  1476. $states = array('stable', 'beta', 'alpha', 'devel');
  1477. if ($type != 'api') {
  1478. $states[] = 'snapshot';
  1479. }
  1480. if (strtolower($value) == 'rc') {
  1481. $this->_stack->push(__FUNCTION__, 'error',
  1482. array('version' => $this->_packageInfo['version']['release']),
  1483. 'RC is not a state, it is a version postfix, try %version%RC1, stability beta');
  1484. }
  1485. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type, 'value' => $value,
  1486. 'types' => $states),
  1487. 'Stability type <%type%> is not a valid stability (%value%), must be one of ' .
  1488. '%types%');
  1489. }
  1490. function _invalidTask($task, $ret, $file)
  1491. {
  1492. switch ($ret[0]) {
  1493. case PEAR_TASK_ERROR_MISSING_ATTRIB :
  1494. $info = array('attrib' => $ret[1], 'task' => $task, 'file' => $file);
  1495. $msg = 'task <%task%> is missing attribute "%attrib%" in file %file%';
  1496. break;
  1497. case PEAR_TASK_ERROR_NOATTRIBS :
  1498. $info = array('task' => $task, 'file' => $file);
  1499. $msg = 'task <%task%> has no attributes in file %file%';
  1500. break;
  1501. case PEAR_TASK_ERROR_WRONG_ATTRIB_VALUE :
  1502. $info = array('attrib' => $ret[1], 'values' => $ret[3],
  1503. 'was' => $ret[2], 'task' => $task, 'file' => $file);
  1504. $msg = 'task <%task%> attribute "%attrib%" has the wrong value "%was%" '.
  1505. 'in file %file%, expecting one of "%values%"';
  1506. break;
  1507. case PEAR_TASK_ERROR_INVALID :
  1508. $info = array('reason' => $ret[1], 'task' => $task, 'file' => $file);
  1509. $msg = 'task <%task%> in file %file% is invalid because of "%reason%"';
  1510. break;
  1511. }
  1512. $this->_stack->push(__FUNCTION__, 'error', $info, $msg);
  1513. }
  1514. function _unknownTask($task, $file)
  1515. {
  1516. $this->_stack->push(__FUNCTION__, 'error', array('task' => $task, 'file' => $file),
  1517. 'Unknown task "%task%" passed in file <file name="%file%">');
  1518. }
  1519. function _subpackageCannotProvideExtension($name)
  1520. {
  1521. $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
  1522. 'Subpackage dependency "%name%" cannot use <providesextension>, ' .
  1523. 'only package dependencies can use this tag');
  1524. }
  1525. function _subpackagesCannotConflict($name)
  1526. {
  1527. $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
  1528. 'Subpackage dependency "%name%" cannot use <conflicts/>, ' .
  1529. 'only package dependencies can use this tag');
  1530. }
  1531. function _cannotProvideExtension($release)
  1532. {
  1533. $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
  1534. '<%release%> packages cannot use <providesextension>, only extbinrelease, extsrcrelease, zendextsrcrelease, and zendextbinrelease can provide a PHP extension');
  1535. }
  1536. function _mustProvideExtension($release)
  1537. {
  1538. $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
  1539. '<%release%> packages must use <providesextension> to indicate which PHP extension is provided');
  1540. }
  1541. function _cannotHaveSrcpackage($release)
  1542. {
  1543. $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
  1544. '<%release%> packages cannot specify a source code package, only extension binaries may use the <srcpackage> tag');
  1545. }
  1546. function _mustSrcPackage($release)
  1547. {
  1548. $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
  1549. '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcpackage>');
  1550. }
  1551. function _mustSrcuri($release)
  1552. {
  1553. $this->_stack->push(__FUNCTION__, 'error', array('release' => $release),
  1554. '<extbinrelease>/<zendextbinrelease> packages must specify a source code package with <srcuri>');
  1555. }
  1556. function _uriDepsCannotHaveVersioning($type)
  1557. {
  1558. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
  1559. '%type%: dependencies with a <uri> tag cannot have any versioning information');
  1560. }
  1561. function _conflictingDepsCannotHaveVersioning($type)
  1562. {
  1563. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
  1564. '%type%: conflicting dependencies cannot have versioning info, use <exclude> to ' .
  1565. 'exclude specific versions of a dependency');
  1566. }
  1567. function _DepchannelCannotBeUri($type)
  1568. {
  1569. $this->_stack->push(__FUNCTION__, 'error', array('type' => $type),
  1570. '%type%: channel cannot be __uri, this is a pseudo-channel reserved for uri ' .
  1571. 'dependencies only');
  1572. }
  1573. function _bundledPackagesMustBeFilename()
  1574. {
  1575. $this->_stack->push(__FUNCTION__, 'error', array(),
  1576. '<bundledpackage> tags must contain only the filename of a package release ' .
  1577. 'in the bundle');
  1578. }
  1579. function _binaryPackageMustBePackagename()
  1580. {
  1581. $this->_stack->push(__FUNCTION__, 'error', array(),
  1582. '<binarypackage> tags must contain the name of a package that is ' .
  1583. 'a compiled version of this extsrc/zendextsrc package');
  1584. }
  1585. function _fileNotFound($file)
  1586. {
  1587. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
  1588. 'File "%file%" in package.xml does not exist');
  1589. }
  1590. function _notInContents($file, $tag)
  1591. {
  1592. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file, 'tag' => $tag),
  1593. '<%tag% name="%file%"> is invalid, file is not in <contents>');
  1594. }
  1595. function _cannotValidateNoPathSet()
  1596. {
  1597. $this->_stack->push(__FUNCTION__, 'error', array(),
  1598. 'Cannot validate files, no path to package file is set (use setPackageFile())');
  1599. }
  1600. function _usesroletaskMustHaveChannelOrUri($role, $tag)
  1601. {
  1602. $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
  1603. '<%tag%> for role "%role%" must contain either <uri>, or <channel> and <package>');
  1604. }
  1605. function _usesroletaskMustHavePackage($role, $tag)
  1606. {
  1607. $this->_stack->push(__FUNCTION__, 'error', array('role' => $role, 'tag' => $tag),
  1608. '<%tag%> for role "%role%" must contain <package>');
  1609. }
  1610. function _usesroletaskMustHaveRoleTask($tag, $type)
  1611. {
  1612. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag, 'type' => $type),
  1613. '<%tag%> must contain <%type%> defining the %type% to be used');
  1614. }
  1615. function _cannotConflictWithAllOs($type)
  1616. {
  1617. $this->_stack->push(__FUNCTION__, 'error', array('tag' => $tag),
  1618. '%tag% cannot conflict with all OSes');
  1619. }
  1620. function _invalidDepGroupName($name)
  1621. {
  1622. $this->_stack->push(__FUNCTION__, 'error', array('name' => $name),
  1623. 'Invalid dependency group name "%name%"');
  1624. }
  1625. function _multipleToplevelDirNotAllowed()
  1626. {
  1627. $this->_stack->push(__FUNCTION__, 'error', array(),
  1628. 'Multiple top-level <dir> tags are not allowed. Enclose them ' .
  1629. 'in a <dir name="/">');
  1630. }
  1631. function _multipleInstallAs($file)
  1632. {
  1633. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
  1634. 'Only one <install> tag is allowed for file "%file%"');
  1635. }
  1636. function _ignoreAndInstallAs($file)
  1637. {
  1638. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
  1639. 'Cannot have both <ignore> and <install> tags for file "%file%"');
  1640. }
  1641. function _analyzeBundledPackages()
  1642. {
  1643. if (!$this->_isValid) {
  1644. return false;
  1645. }
  1646. if (!$this->_pf->getPackageType() == 'bundle') {
  1647. return false;
  1648. }
  1649. if (!isset($this->_pf->_packageFile)) {
  1650. return false;
  1651. }
  1652. $dir_prefix = dirname($this->_pf->_packageFile);
  1653. $common = new PEAR_Common;
  1654. $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
  1655. array($common, 'log');
  1656. $info = $this->_pf->getContents();
  1657. $info = $info['bundledpackage'];
  1658. if (!is_array($info)) {
  1659. $info = array($info);
  1660. }
  1661. $pkg = &new PEAR_PackageFile($this->_pf->_config);
  1662. foreach ($info as $package) {
  1663. if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $package)) {
  1664. $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $package);
  1665. $this->_isValid = 0;
  1666. continue;
  1667. }
  1668. call_user_func_array($log, array(1, "Analyzing bundled package $package"));
  1669. PEAR::pushErrorHandling(PEAR_ERROR_RETURN);
  1670. $ret = $pkg->fromAnyFile($dir_prefix . DIRECTORY_SEPARATOR . $package,
  1671. PEAR_VALIDATE_NORMAL);
  1672. PEAR::popErrorHandling();
  1673. if (PEAR::isError($ret)) {
  1674. call_user_func_array($log, array(0, "ERROR: package $package is not a valid " .
  1675. 'package'));
  1676. $inf = $ret->getUserInfo();
  1677. if (is_array($inf)) {
  1678. foreach ($inf as $err) {
  1679. call_user_func_array($log, array(1, $err['message']));
  1680. }
  1681. }
  1682. return false;
  1683. }
  1684. }
  1685. return true;
  1686. }
  1687. function _analyzePhpFiles()
  1688. {
  1689. if (!$this->_isValid) {
  1690. return false;
  1691. }
  1692. if (!isset($this->_pf->_packageFile)) {
  1693. $this->_cannotValidateNoPathSet();
  1694. return false;
  1695. }
  1696. $dir_prefix = dirname($this->_pf->_packageFile);
  1697. $common = new PEAR_Common;
  1698. $log = isset($this->_pf->_logger) ? array(&$this->_pf->_logger, 'log') :
  1699. array(&$common, 'log');
  1700. $info = $this->_pf->getContents();
  1701. if (!$info || !isset($info['dir']['file'])) {
  1702. $this->_tagCannotBeEmpty('contents><dir');
  1703. return false;
  1704. }
  1705. $info = $info['dir']['file'];
  1706. if (isset($info['attribs'])) {
  1707. $info = array($info);
  1708. }
  1709. $provides = array();
  1710. foreach ($info as $fa) {
  1711. $fa = $fa['attribs'];
  1712. $file = $fa['name'];
  1713. if (!file_exists($dir_prefix . DIRECTORY_SEPARATOR . $file)) {
  1714. $this->_fileNotFound($dir_prefix . DIRECTORY_SEPARATOR . $file);
  1715. $this->_isValid = 0;
  1716. continue;
  1717. }
  1718. if (in_array($fa['role'], PEAR_Installer_Role::getPhpRoles()) && $dir_prefix) {
  1719. call_user_func_array($log, array(1, "Analyzing $file"));
  1720. $srcinfo = $this->analyzeSourceCode($dir_prefix . DIRECTORY_SEPARATOR . $file);
  1721. if ($srcinfo) {
  1722. $provides = array_merge($provides, $this->_buildProvidesArray($srcinfo));
  1723. }
  1724. }
  1725. }
  1726. $this->_packageName = $pn = $this->_pf->getPackage();
  1727. $pnl = strlen($pn);
  1728. foreach ($provides as $key => $what) {
  1729. if (isset($what['explicit']) || !$what) {
  1730. // skip conformance checks if the provides entry is
  1731. // specified in the package.xml file
  1732. continue;
  1733. }
  1734. extract($what);
  1735. if ($type == 'class') {
  1736. if (!strncasecmp($name, $pn, $pnl)) {
  1737. continue;
  1738. }
  1739. $this->_stack->push(__FUNCTION__, 'warning',
  1740. array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
  1741. 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
  1742. } elseif ($type == 'function') {
  1743. if (strstr($name, '::') || !strncasecmp($name, $pn, $pnl)) {
  1744. continue;
  1745. }
  1746. $this->_stack->push(__FUNCTION__, 'warning',
  1747. array('file' => $file, 'type' => $type, 'name' => $name, 'package' => $pn),
  1748. 'in %file%: %type% "%name%" not prefixed with package name "%package%"');
  1749. }
  1750. }
  1751. return $this->_isValid;
  1752. }
  1753. /**
  1754. * Analyze the source code of the given PHP file
  1755. *
  1756. * @param string Filename of the PHP file
  1757. * @param boolean whether to analyze $file as the file contents
  1758. * @return mixed
  1759. */
  1760. function analyzeSourceCode($file, $string = false)
  1761. {
  1762. if (!function_exists("token_get_all")) {
  1763. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
  1764. 'Parser error: token_get_all() function must exist to analyze source code, PHP may have been compiled with --disable-tokenizer');
  1765. return false;
  1766. }
  1767. if (!defined('T_DOC_COMMENT')) {
  1768. define('T_DOC_COMMENT', T_COMMENT);
  1769. }
  1770. if (!defined('T_INTERFACE')) {
  1771. define('T_INTERFACE', -1);
  1772. }
  1773. if (!defined('T_IMPLEMENTS')) {
  1774. define('T_IMPLEMENTS', -1);
  1775. }
  1776. if ($string) {
  1777. $contents = $file;
  1778. } else {
  1779. if (!$fp = @fopen($file, "r")) {
  1780. return false;
  1781. }
  1782. fclose($fp);
  1783. $contents = file_get_contents($file);
  1784. }
  1785. // Silence this function so we can catch PHP Warnings and show our own custom message
  1786. $tokens = @token_get_all($contents);
  1787. if (isset($php_errormsg)) {
  1788. if (isset($this->_stack)) {
  1789. $pn = $this->_pf->getPackage();
  1790. $this->_stack->push(__FUNCTION__, 'warning',
  1791. array('file' => $file, 'package' => $pn),
  1792. 'in %file%: Could not process file for unkown reasons,' .
  1793. ' possibly a PHP parse error in %file% from %package%');
  1794. }
  1795. }
  1796. /*
  1797. for ($i = 0; $i < sizeof($tokens); $i++) {
  1798. @list($token, $data) = $tokens[$i];
  1799. if (is_string($token)) {
  1800. var_dump($token);
  1801. } else {
  1802. print token_name($token) . ' ';
  1803. var_dump(rtrim($data));
  1804. }
  1805. }
  1806. */
  1807. $look_for = 0;
  1808. $paren_level = 0;
  1809. $bracket_level = 0;
  1810. $brace_level = 0;
  1811. $lastphpdoc = '';
  1812. $current_class = '';
  1813. $current_interface = '';
  1814. $current_class_level = -1;
  1815. $current_function = '';
  1816. $current_function_level = -1;
  1817. $declared_classes = array();
  1818. $declared_interfaces = array();
  1819. $declared_functions = array();
  1820. $declared_methods = array();
  1821. $used_classes = array();
  1822. $used_functions = array();
  1823. $extends = array();
  1824. $implements = array();
  1825. $nodeps = array();
  1826. $inquote = false;
  1827. $interface = false;
  1828. for ($i = 0; $i < sizeof($tokens); $i++) {
  1829. if (is_array($tokens[$i])) {
  1830. list($token, $data) = $tokens[$i];
  1831. } else {
  1832. $token = $tokens[$i];
  1833. $data = '';
  1834. }
  1835. if ($inquote) {
  1836. if ($token != '"' && $token != T_END_HEREDOC) {
  1837. continue;
  1838. } else {
  1839. $inquote = false;
  1840. continue;
  1841. }
  1842. }
  1843. switch ($token) {
  1844. case T_WHITESPACE :
  1845. continue;
  1846. case ';':
  1847. if ($interface) {
  1848. $current_function = '';
  1849. $current_function_level = -1;
  1850. }
  1851. break;
  1852. case '"':
  1853. case T_START_HEREDOC:
  1854. $inquote = true;
  1855. break;
  1856. case T_CURLY_OPEN:
  1857. case T_DOLLAR_OPEN_CURLY_BRACES:
  1858. case '{': $brace_level++; continue 2;
  1859. case '}':
  1860. $brace_level--;
  1861. if ($current_class_level == $brace_level) {
  1862. $current_class = '';
  1863. $current_class_level = -1;
  1864. }
  1865. if ($current_function_level == $brace_level) {
  1866. $current_function = '';
  1867. $current_function_level = -1;
  1868. }
  1869. continue 2;
  1870. case '[': $bracket_level++; continue 2;
  1871. case ']': $bracket_level--; continue 2;
  1872. case '(': $paren_level++; continue 2;
  1873. case ')': $paren_level--; continue 2;
  1874. case T_INTERFACE:
  1875. $interface = true;
  1876. case T_CLASS:
  1877. if (($current_class_level != -1) || ($current_function_level != -1)) {
  1878. if (isset($this->_stack)) {
  1879. $this->_stack->push(__FUNCTION__, 'error', array('file' => $file),
  1880. 'Parser error: invalid PHP found in file "%file%"');
  1881. } else {
  1882. PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
  1883. PEAR_COMMON_ERROR_INVALIDPHP);
  1884. }
  1885. return false;
  1886. }
  1887. case T_FUNCTION:
  1888. case T_NEW:
  1889. case T_EXTENDS:
  1890. case T_IMPLEMENTS:
  1891. $look_for = $token;
  1892. continue 2;
  1893. case T_STRING:
  1894. if (version_compare(zend_version(), '2.0', '<')) {
  1895. if (in_array(strtolower($data),
  1896. array('public', 'private', 'protected', 'abstract',
  1897. 'interface', 'implements', 'throw')
  1898. )
  1899. ) {
  1900. if (isset($this->_stack)) {
  1901. $this->_stack->push(__FUNCTION__, 'warning', array(
  1902. 'file' => $file),
  1903. 'Error, PHP5 token encountered in %file%,' .
  1904. ' analysis should be in PHP5');
  1905. } else {
  1906. PEAR::raiseError('Error: PHP5 token encountered in ' . $file .
  1907. 'packaging should be done in PHP 5');
  1908. return false;
  1909. }
  1910. }
  1911. }
  1912. if ($look_for == T_CLASS) {
  1913. $current_class = $data;
  1914. $current_class_level = $brace_level;
  1915. $declared_classes[] = $current_class;
  1916. } elseif ($look_for == T_INTERFACE) {
  1917. $current_interface = $data;
  1918. $current_class_level = $brace_level;
  1919. $declared_interfaces[] = $current_interface;
  1920. } elseif ($look_for == T_IMPLEMENTS) {
  1921. $implements[$current_class] = $data;
  1922. } elseif ($look_for == T_EXTENDS) {
  1923. $extends[$current_class] = $data;
  1924. } elseif ($look_for == T_FUNCTION) {
  1925. if ($current_class) {
  1926. $current_function = "$current_class::$data";
  1927. $declared_methods[$current_class][] = $data;
  1928. } elseif ($current_interface) {
  1929. $current_function = "$current_interface::$data";
  1930. $declared_methods[$current_interface][] = $data;
  1931. } else {
  1932. $current_function = $data;
  1933. $declared_functions[] = $current_function;
  1934. }
  1935. $current_function_level = $brace_level;
  1936. $m = array();
  1937. } elseif ($look_for == T_NEW) {
  1938. $used_classes[$data] = true;
  1939. }
  1940. $look_for = 0;
  1941. continue 2;
  1942. case T_VARIABLE:
  1943. $look_for = 0;
  1944. continue 2;
  1945. case T_DOC_COMMENT:
  1946. case T_COMMENT:
  1947. if (preg_match('!^/\*\*\s!', $data)) {
  1948. $lastphpdoc = $data;
  1949. if (preg_match_all('/@nodep\s+(\S+)/', $lastphpdoc, $m)) {
  1950. $nodeps = array_merge($nodeps, $m[1]);
  1951. }
  1952. }
  1953. continue 2;
  1954. case T_DOUBLE_COLON:
  1955. $token = $tokens[$i - 1][0];
  1956. if (!($token == T_WHITESPACE || $token == T_STRING || $token == T_STATIC)) {
  1957. if (isset($this->_stack)) {
  1958. $this->_stack->push(__FUNCTION__, 'warning', array('file' => $file),
  1959. 'Parser error: invalid PHP found in file "%file%"');
  1960. } else {
  1961. PEAR::raiseError("Parser error: invalid PHP found in file \"$file\"",
  1962. PEAR_COMMON_ERROR_INVALIDPHP);
  1963. }
  1964. return false;
  1965. }
  1966. $class = $tokens[$i - 1][1];
  1967. if (strtolower($class) != 'parent') {
  1968. $used_classes[$class] = true;
  1969. }
  1970. continue 2;
  1971. }
  1972. }
  1973. return array(
  1974. "source_file" => $file,
  1975. "declared_classes" => $declared_classes,
  1976. "declared_interfaces" => $declared_interfaces,
  1977. "declared_methods" => $declared_methods,
  1978. "declared_functions" => $declared_functions,
  1979. "used_classes" => array_diff(array_keys($used_classes), $nodeps),
  1980. "inheritance" => $extends,
  1981. "implements" => $implements,
  1982. );
  1983. }
  1984. /**
  1985. * Build a "provides" array from data returned by
  1986. * analyzeSourceCode(). The format of the built array is like
  1987. * this:
  1988. *
  1989. * array(
  1990. * 'class;MyClass' => 'array('type' => 'class', 'name' => 'MyClass'),
  1991. * ...
  1992. * )
  1993. *
  1994. *
  1995. * @param array $srcinfo array with information about a source file
  1996. * as returned by the analyzeSourceCode() method.
  1997. *
  1998. * @return void
  1999. *
  2000. * @access private
  2001. *
  2002. */
  2003. function _buildProvidesArray($srcinfo)
  2004. {
  2005. if (!$this->_isValid) {
  2006. return array();
  2007. }
  2008. $providesret = array();
  2009. $file = basename($srcinfo['source_file']);
  2010. $pn = isset($this->_pf) ? $this->_pf->getPackage() : '';
  2011. $pnl = strlen($pn);
  2012. foreach ($srcinfo['declared_classes'] as $class) {
  2013. $key = "class;$class";
  2014. if (isset($providesret[$key])) {
  2015. continue;
  2016. }
  2017. $providesret[$key] =
  2018. array('file'=> $file, 'type' => 'class', 'name' => $class);
  2019. if (isset($srcinfo['inheritance'][$class])) {
  2020. $providesret[$key]['extends'] =
  2021. $srcinfo['inheritance'][$class];
  2022. }
  2023. }
  2024. foreach ($srcinfo['declared_methods'] as $class => $methods) {
  2025. foreach ($methods as $method) {
  2026. $function = "$class::$method";
  2027. $key = "function;$function";
  2028. if ($method{0} == '_' || !strcasecmp($method, $class) ||
  2029. isset($providesret[$key])) {
  2030. continue;
  2031. }
  2032. $providesret[$key] =
  2033. array('file'=> $file, 'type' => 'function', 'name' => $function);
  2034. }
  2035. }
  2036. foreach ($srcinfo['declared_functions'] as $function) {
  2037. $key = "function;$function";
  2038. if ($function{0} == '_' || isset($providesret[$key])) {
  2039. continue;
  2040. }
  2041. if (!strstr($function, '::') && strncasecmp($function, $pn, $pnl)) {
  2042. $warnings[] = "in1 " . $file . ": function \"$function\" not prefixed with package name \"$pn\"";
  2043. }
  2044. $providesret[$key] =
  2045. array('file'=> $file, 'type' => 'function', 'name' => $function);
  2046. }
  2047. return $providesret;
  2048. }
  2049. }